技术博客总结
技术概述
JWT是一种实现无状态服务的工具,用于代替session实现登录状态的存储,减少了服务器存储sesison的开销,也解决了跨域问题。
技术详述
实现原理如图所示
一个比较通用的做法就是将JWT的生成和解析放在一个工具类中,便于全局使用
@Component
public class JwtUtil {
@Autowired
UserMapper userMapper;
// application.yml中关于jwt的配置属性
public static final String secretKey = "SECRET_KEY";
// token 1小时候过期
public static final Long expire = 60 * 60 * 1000L;
/*
创建Token
*/
public String createToken(User user){
// 根据登录的User对象信息生成Token
JwtBuilder jwtBuilder = Jwts.builder()
.setId(user.getId() + "")
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS256, secretKey);
// 向token中添加键值对信息
return jwtBuilder.compact();
}
/*
解析Token
*/
public User parseToken(String token){
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
// 从解析结果中获得键值对信息
String id = claims.getId();
System.out.println(id);
Integer userId = Integer.parseInt(id);
// 从数据库中查找User
return userMapper.getUserById(userId);
}
/*
验证token
*/
public boolean validateToken(String token){
try{
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
}
catch(SignatureException e){
ExceptionThrower.cast(ResponseCode.INVALID_TOKEN);
}
catch(ExpiredJwtException e){
ExceptionThrower.cast(ResponseCode.TOKEN_EXPIRED);
}
return false;
}
}
之后添加一个拦截器,负责拦截所有请求,并判断请求Header中是否有Token,若有则解析出操作用户。
// 部分代码
@Slf4j
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Autowired
JwtUtil jwtUtil;
public static final String ADMIN = "ADMIN";
public static final String USER = "USER";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
return true;
}
String token = request.getHeader("Authorization");
String requestURI = request.getRequestURI();
log.info("接受到token:"+token);
log.info("请求路径:"+ requestURI);
// 鉴权判断
}
}
之后的工作便是在登录时生成Token并返回给前端,前端发送axios请求时将token放入header中即可。
问题与解决
我们的项目中使用jwt的原因是Shiro在跨域情况下无法保持用户session一致,因此shiro能实现的,jwt也一定要实现,例如鉴权操作。
经过网上查阅相关资料,jwt的鉴权也可以通过注解来实现。首先先定义几个注解
/**
* 不需要登录
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
/**
* 需要具备某些角色
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRoles {
String[] roles() default {};
}
之后在拦截的时候,根据要访问的控制器方法获取其注解,再根据token获取用户对象信息,根据用户权限进行拦截即可。
// 通过请求
if (method.isAnnotationPresent(PassToken.class)){
log.info("不需要权限,放行");
return true;
}
// 需要登录
if (method.isAnnotationPresent(RequireAuthorization.class))
{
log.info("需要USER权限");
return requireUSERRole(token);
}
总结
没啥特别难理解的东西,就是Token的刷新机制暂时没有运用到项目中,待以后慢慢研究。