登录控制 : Token自动刷新功能,达到续期目的
spring boot 项目引入JWT 校验 : 基于JWT 使用Spring boot项目权限校验
前言:
- 为达成公司内部需求 : APP 登录. PC端登录 可以保留用户登录状态, 用户持续使用中,应在用户无感知的情况下自动续期token.
设计流程实现:
摘要说明:
token时长例如 = 30min
refreshToken时长务必大于token时长,这里我取值 = 60min
这里当token失效时: 后台会在返回数据时给headers中放入过期提醒状态.
前端需要使用响应前置拦截,并判断headers中的参数是否需要更新token,若需要更新token则需要用refreshToken换取新的token和新的refreshToken.
最终达成目标流程:
代码实现:
-
这里使用 spring boot 2.1.13 版本.
-
省略前端登录,存储token,根据后端状态刷新token功能, 只实现后台拦截器.
/**
* @author zly
* Spring MVC 请求拦截配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 认证组件注册到IOC
*
* @return AuthenticationInterceptor
*/
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
/**
* 过滤器放行文档文件
*
* @param registry 资源处理器注册对象
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/* 静态资源文件放行处理 */
}
/**
* 统一异常处理
*
* @param exceptionResolvers 异常信息
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
/*统一异常处理 */
/* 在这里处理当token失效所抛出的异常 , 修改其响应的httpStatus */
}
/**
* 跨域放行
*
* @param registry CorsRegistry对象
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
//是否发送Cookie信息
.allowCredentials(true)
//放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "PUT", "DELETE")
//放行哪些原始域(头部信息)
.allowedHeaders("*");
}
/**
* 添加拦截器
*
* @param registry 拦截器注册对象
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
//所有访问路径都要进行判断
.addPathPatterns("/**");
}
/**
* 返回响应结果
*
* @param response 封装响应对象
* @param result 返回结果
*/
private void responseResult(HttpServletResponse response, Result result, HttpStatus status) {
/* 统一响应的封装 */
}
}
/**
* 拦截器规则实例
*
* @Author: zly
* @Date: 2020/3/27 15:23
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Resource
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
// 如果不是映射到方法直接通过,防止静态资源被拦截
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//检查是否有CheckLogin注释,有则跳过认证 ** 自定义注解
if (method.isAnnotationPresent(CheckLogin.class)) {
CheckLogin checkLogin = method.getAnnotation(CheckLogin.class);
if (checkLogin.required()) {
return true;
}
}
//检查有没有需要用户权限的注解 ** 自定义注解
if (method.isAnnotationPresent(CheckToken.class)) {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("token");
CheckToken checkToken = method.getAnnotation(CheckToken.class);
if (checkToken.required()) {
// 执行认证
if (StringUtils.isBlank(token)) {
throw new AuthException("签名校验失败,请重新登录");
}
User user = JwtUtil.decode(token, User.class);
if (null == user) {
//取出刷新token
String refreshToken = httpServletRequest.getHeader("refreshToken");
if (StringUtils.isBlank(refreshToken)) {
throw new AuthException("签名已过期,请重新登录");
}
User refreshUser = JwtUtil.decode(refreshToken, User.class);
if (null == refreshUser) {
throw new AuthException("签名已过期,请重新登录");
}
/* 如果是获取刷新token的URI则跳过 */
if (!httpServletRequest.getRequestURI().contains(REFRESH_TOKEN)) {
httpServletResponse.setHeader("status", AUTHORIZATION_EXPIRES);
}
user = refreshUser;
}
User currUser = userService.findById(user.getId());
if (currUser == null) {
throw new AuthException("不合法的签名,请重新登录");
}
if (!user.getPassword().equals(currUser.getPassword())) {
throw new AuthException("密码已修改,请重新登录!");
}
// 权限判定
RequiredPermission requiredPermission = handlerMethod.getMethod().getAnnotation(RequiredPermission.class);
// 如果方法上的注解为空 则获取类的注解
if (requiredPermission == null) {
requiredPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequiredPermission.class);
}
// 如果标记了注解,则判断权限 ** 自定义权限注解
if (requiredPermission != null && StringUtils.isNotBlank(requiredPermission.value())) {
// redis或数据库 中获取该用户的权限信息 并判断是否有权限
Set<String> permissionSet = userService.getPermissionSet(user.getId());
if (CollectionUtils.isEmpty(permissionSet) || !permissionSet.contains(requiredPermission.value())) {
throw new ServiceException("权限不足!");
}
}
}
}
return true;
}
}
总结
掌握spring boot => 实现WebMvcConfigurer的接口来达成路径的拦截与对跨域的处理, 包括对在请求拦截中添加的操作: 一般鉴定权限的方法可以嵌入其中.
掌握spring boot => 统一异常处理, 返回数据的结构统一化, 减少业务代码中重复出现的try-catch,造成的代码冗余.
掌握对于使用JWT TOKEN的进行登录认证基础使用.
以上就是token自刷新的核心推导思路和核心实现代码. 若有不足,烦请指出…