From d4408ef28eb5582091ef3f978769db592a2af27b Mon Sep 17 00:00:00 2001 From: "Yangkai.Shen" <237497819@qq.com> Date: Fri, 7 Dec 2018 17:44:16 +0800 Subject: [PATCH] =?UTF-8?q?:card=5Ffile=5Fbox:=20=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E9=83=A8=E5=88=86=E3=80=81JWT=E5=B7=A5=E5=85=B7=E7=B1=BB?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rbac/security/common/ApiResponse.java | 131 ++++++++++++++++++ .../rbac/security/common/BaseException.java | 47 +++++++ .../rbac/security/common/IStatus.java | 32 +++++ .../xkcoding/rbac/security/common/Status.java | 65 +++++++++ .../rbac/security/config/JwtConfig.java | 31 +++++ .../xkcoding/rbac/security/util/JwtUtil.java | 87 ++++++++++++ .../rbac/security/util/ResponseUtil.java | 47 +++++++ 7 files changed, 440 insertions(+) create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java create mode 100644 spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java new file mode 100644 index 0000000..8d743c3 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java @@ -0,0 +1,131 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + *

+ * 通用的 API 接口封装 + *

+ * + * @package: com.xkcoding.rbac.security.common + * @description: 通用的 API 接口封装 + * @author: yangkai.shen + * @date: Created in 2018-12-07 14:55 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Data +public class ApiResponse implements Serializable { + private static final long serialVersionUID = 8993485788201922830L; + + /** + * 状态码 + */ + private Integer code; + + /** + * 返回内容 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + /** + * 无参构造函数 + */ + private ApiResponse() { + + } + + /** + * 全参构造函数 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + */ + private ApiResponse(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + /** + * 构造一个自定义的API返回 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse of(Integer code, String message, Object data) { + return new ApiResponse(code, message, data); + } + + /** + * 构造一个成功且不带数据的API返回 + * + * @return ApiResponse + */ + public static ApiResponse ofSuccess() { + return ofSuccess(null); + } + + /** + * 构造一个成功且带数据的API返回 + * + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofSuccess(Object data) { + return ofStatus(Status.SUCCESS, data); + } + + /** + * 构造一个成功且自定义消息的API返回 + * + * @param message 返回内容 + * @return ApiResponse + */ + public static ApiResponse ofMessage(String message) { + return of(Status.SUCCESS.getCode(), message, null); + } + + /** + * 构造一个有状态的API返回 + * + * @param status 状态 {@link Status} + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status) { + return ofStatus(status, null); + } + + /** + * 构造一个有状态且带数据的API返回 + * + * @param status 状态 {@link IStatus} + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofStatus(IStatus status, Object data) { + return of(status.getCode(), status.getMessage(), data); + } + + /** + * 构造一个异常的API返回 + * + * @param t 异常 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t) { + return of(t.getCode(), t.getMessage(), t.getData()); + } +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java new file mode 100644 index 0000000..77a2901 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java @@ -0,0 +1,47 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 异常基类 + *

+ * + * @package: com.xkcoding.rbac.security.common + * @description: 异常基类 + * @author: yangkai.shen + * @date: Created in 2018-12-07 14:57 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class BaseException extends RuntimeException { + private Integer code; + private String message; + private Object data; + + public BaseException(Status status) { + super(status.getMessage()); + this.code = status.getCode(); + this.message = status.getMessage(); + } + + public BaseException(Status status, Object data) { + this(status); + this.data = data; + } + + public BaseException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public BaseException(Integer code, String message, Object data) { + this(code, message); + this.data = data; + } +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java new file mode 100644 index 0000000..eefd3a2 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java @@ -0,0 +1,32 @@ +package com.xkcoding.rbac.security.common; + +/** + *

+ * REST API 错误码接口 + *

+ * + * @package: com.xkcoding.rbac.security.common + * @description: REST API 错误码接口 + * @author: yangkai.shen + * @date: Created in 2018-12-07 14:35 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +public interface IStatus { + + /** + * 状态码 + * + * @return 状态码 + */ + Integer getCode(); + + /** + * 返回信息 + * + * @return 返回信息 + */ + String getMessage(); + +} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java new file mode 100644 index 0000000..6b0a196 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java @@ -0,0 +1,65 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Getter; + +/** + *

+ * 通用状态码 + *

+ * + * @package: com.xkcoding.rbac.security.common + * @description: 通用状态码 + * @author: yangkai.shen + * @date: Created in 2018-12-07 14:31 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Getter +public enum Status implements IStatus { + /** + * 操作成功 + */ + SUCCESS(200, "操作成功"), + /** + * 操作异常 + */ + ERROR(500, "操作异常"), + + /** + * 退出成功 + */ + LOGOUT(200, "退出成功"); + + /** + * 状态码 + */ + private Integer code; + + /** + * 返回信息 + */ + private String message; + + Status(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static Status fromCode(Integer code) { + Status[] statuses = Status.values(); + for (Status status : statuses) { + if (status.getCode() + .equals(code)) { + return status; + } + } + return SUCCESS; + } + + @Override + public String toString() { + return String.format(" Status:{code=%s, message=%s} ", getCode(), getMessage()); + } + +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java new file mode 100644 index 0000000..0bb1df1 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java @@ -0,0 +1,31 @@ +package com.xkcoding.rbac.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

+ * JWT 配置 + *

+ * + * @package: com.xkcoding.rbac.security.config + * @description: JWT 配置 + * @author: yangkai.shen + * @date: Created in 2018-12-07 13:42 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@ConfigurationProperties(prefix = "jwt.config") +@Data +public class JwtConfig { + /** + * jwt 加密 key, 默认值:xkcoding. + */ + private String key = "xkcoding"; + + /** + * jwt 过期时间, 默认值:600000 {@code 10 分钟}. + */ + private Long ttl = 600000L; +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java new file mode 100644 index 0000000..4514b35 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java @@ -0,0 +1,87 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.core.date.DateUtil; +import com.xkcoding.rbac.security.config.JwtConfig; +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Date; + +/** + *

+ * JWT 工具类 + *

+ * + * @package: com.xkcoding.rbac.security.util + * @description: JWT 工具类 + * @author: yangkai.shen + * @date: Created in 2018-12-07 13:42 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@EnableConfigurationProperties(JwtConfig.class) +@Configuration +@Slf4j +public class JwtUtil { + private final JwtConfig jwtConfig; + + @Autowired + public JwtUtil(JwtConfig jwtConfig) { + this.jwtConfig = jwtConfig; + } + + /** + * 创建JWT + * + * @param id 用户id + * @param subject 用户名 + * @param roles 用户角色 + * @return JWT + */ + public String createJWT(String id, String subject, String roles) { + Date now = new Date(); + JwtBuilder builder = Jwts.builder() + .setId(id) + .setSubject(subject) + .setIssuedAt(now) + .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) + .claim("roles", roles); + if (jwtConfig.getTtl() > 0) { + builder.setExpiration(DateUtil.offsetMillisecond(now, jwtConfig.getTtl() + .intValue())); + } + return builder.compact(); + } + + /** + * 解析JWT + * + * @param jwt JWT + * @return {@link Claims} + */ + public Claims parseJWT(String jwt) { + Claims claims = null; + try { + claims = Jwts.parser() + .setSigningKey(jwtConfig.getKey()) + .parseClaimsJws(jwt) + .getBody(); + } catch (ExpiredJwtException e) { + log.error("Token 已过期"); + } catch (UnsupportedJwtException e) { + log.error("不支持的 Token"); + } catch (MalformedJwtException e) { + log.error("Token 无效"); + } catch (SignatureException e) { + log.error("无效的 Token 签名"); + } catch (IllegalArgumentException e) { + log.error("Token 参数不存在"); + } + return claims; + } + +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java new file mode 100644 index 0000000..f2fa4e4 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java @@ -0,0 +1,47 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.json.JSONUtil; +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.IStatus; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

+ * Response 通用工具类 + *

+ * + * @package: com.xkcoding.rbac.security.util + * @description: Response 通用工具类 + * @author: yangkai.shen + * @date: Created in 2018-12-07 17:37 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class ResponseUtil { + + /** + * 往 response 写出 json + * + * @param response 响应 + * @param status 状态 + * @param data 返回数据 + */ + public static void renderJson(HttpServletResponse response, IStatus status, Object data) { + try { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(200); + + response.getWriter() + .write(JSONUtil.toJsonStr(ApiResponse.ofStatus(status, data))); + } catch (IOException e) { + log.error("Response写出JSON异常,", e); + } + } +} \ No newline at end of file