Loading... # SpringBoot 配合 Jwt 实现请求鉴权 ## Session 与 JWT ### Session 在初学Servlet或Spring时,采用的往往是通过Session来实现登录状态保持以及用户信息的存储,但问题在于Session是有有效期的,当有一段时间不访问后Session就会被服务器销毁(Tomcat默认20min),且会占用服务器内存。 ### JWT > JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. > JWT是RFC 7519的实现方法,用于在各方之间作为JSON对象安全地传输信息。 广义上,JWT是一个标准的名称;狭义上,JWT指的就是用来传递的那个Token字符串。 与Session相比,可以将信息存储在JWT的payload中,只要JWT不过期,则用户的登录状态就不会过期(无状态),当然这也会带来一个问题,即因为JWT无法主动失效,如何控制单用户登录数量。 JWT使用数字签名对上述信息进行加密,可以使用HMAC算法,或是RSA等公私钥来实现。 ![Jwt.png](https://cdn2.feczine.cn/2023/07/02/64a15a41862af.png) ## JWT 结构 一个JWT由以下三部分组成: - Header - Payload - Signature 每个部分都表示一个base64编码的字符串,用点('`.`')作为分隔符分隔。 下面以一个JWT为例: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c` 第一部分 Header:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` 实际上是 JSON ```json { "alg": "HS256", "typ": "JWT" } ``` 在BASE64运算后的结果 同理,第二部分 PAYLOAD:`eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ` 是 JSON ```json { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } ``` 第三部分 VERIFY SIGNATURE: HMACSHA256 使用密钥 `your-256-bit-secret` 对前两部分的BASE64进行签名 `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ`,得到BASE64结果:`SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV/adQssw5c=` 需要注意的是,这里是对签名之后的结果进行BASE64运算,而不是对结果的16进制表示进行BASE64 ## 使用 ### 引入依赖 ```xml <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> ``` ### 工具类 ```java /** * @author MashiroT */ public class JwtUtils { private static final String ALGORITHM_SALT = "SALT"; private static final int EXPIRATION = 30 * 60 * 1000; public static String createToken(Integer uid, String username, Date termStartDate) { return JWT.create() .withClaim("uid", uid) .withClaim("username", username) .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION)) .withIssuer("mashirot") .withIssuedAt(new Date()) .sign(Algorithm.HMAC512(ALGORITHM_SALT)); } public static DecodedJWT getVerifier(String authToken) throws JWTVerificationException { return JWT.require(Algorithm.HMAC512(ALGORITHM_SALT)) .withIssuer("mashirot") .build() .verify(authToken); } } ``` ### 注解 ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface TokenRequired { boolean required() default true; } ``` 在需要鉴权的Controller方法上添加此注解 ### 拦截器类 ```java @Configuration public class AuthInterceptorConfig implements WebMvcConfigurer { @Bean public AuthInterceptor authInterceptorFactory() { return new AuthInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptorFactory()) .addPathPatterns("/**"); } } ``` ```java @Component public class AuthInterceptor implements HandlerInterceptor { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行 OPTIONS 请求 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } // 判断是否请求Controller的方法 if (!(handler instanceof HandlerMethod)) { return true; } String authToken = request.getHeader("Authorization"); Method method = ((HandlerMethod) handler).getMethod(); if (method.isAnnotationPresent(TokenRequired.class)) { TokenRequired tokenRequired = method.getAnnotation(TokenRequired.class); // 判断是否需要鉴权 if (tokenRequired.required()) { if (authToken == null) { // 返回失败结果 response.getWriter().write(OBJECT_MAPPER.writeValueAsString(Result.failed(StatusCodeConstants.AUTH_VERIFY_FAILED, null))); return false; } // Authorization: Bearer XXXXXX try { authToken = authToken.split(" ")[1]; DecodedJWT decodedJwt = JwtUtils.getVerifier(authToken); Integer uid = decodedJwt.getClaim("uid").asInt(); String username = decodedJwt.getClaim("username").asString(); request.getSession().setAttribute("uid", uid); request.getSession().setAttribute("username", username); } catch (Exception e) { response.getWriter().write(OBJECT_MAPPER.writeValueAsString(Result.failed(StatusCodeConstants.AUTH_VERIFY_FAILED, null))); return false; } } return true; } return true; } } ``` <br /><br /><br /> 参考: 1. https://www.baeldung.com/java-auth0-jwt 最后修改:2023 年 07 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 2 本作品采用 CC BY-NC-SA 4.0 International License 进行许可。