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
{
"alg": "HS256",
"typ": "JWT"
}
在BASE64运算后的结果
同理,第二部分 PAYLOAD:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
是 JSON
{
"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 /> 参考:
- https://www.baeldung.com/java-auth0-jwt