SpringBoot 配合 Jwt 实现请求鉴权

2023-10-18 15:41:47 浏览数 (3)

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 结构

一个JWT由以下三部分组成:

  • Header
  • Payload
  • Signature

每个部分都表示一个base64编码的字符串,用点('.')作为分隔符分隔。

下面以一个JWT为例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

第一部分 Header:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 实际上是 JSON

代码语言:javascript复制
{
  "alg": "HS256",
  "typ": "JWT"
}

在BASE64运算后的结果

同理,第二部分 PAYLOAD:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ 是 JSON

代码语言:javascript复制
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

第三部分 VERIFY SIGNATURE: HMACSHA256 使用密钥 your-256-bit-secret 对前两部分的BASE64进行签名 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ,得到BASE64结果:SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV/adQssw5c=

需要注意的是,这里是对签名之后的结果进行BASE64运算,而不是对结果的16进制表示进行BASE64

使用

引入依赖

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

工具类

代码语言:javascript复制
/**
 * @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);
    }
}

注解

代码语言:javascript复制
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenRequired {
    boolean required() default true;
}

在需要鉴权的Controller方法上添加此注解

拦截器类

代码语言:javascript复制
@Configuration
public class AuthInterceptorConfig implements WebMvcConfigurer {

    @Bean
    public AuthInterceptor authInterceptorFactory() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptorFactory())
                .addPathPatterns("/**");
    }
}
代码语言:javascript复制
@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

1 人点赞