⛰️个人主页: 蒾酒
🔥系列专栏:《spring boot实战》
🌊山高路远,行路漫漫,终有归途。
目录
写在前面
流程分析
需要清楚的
实现步骤
1.定义拦截器
2.创建拦截器链配置类
3.配置拦截器链顺序
4.配置拦截排除项
最后
本文介绍了spring boot后端服务开发中有关如何设计拦截器的思路,坚持看完相信对你有帮助。
同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。
用户在进行登陆后服务器会发放token等信息一起返回给前端,前端会进行保存,那么token里面是携带一些有关用户的身份等信息的,用户端在请求后端时需要在请求头携带token,请求先被拦截器截获,只有经过多重拦截器校验通过后才可以执行对应功能接口,否则会抛出异常返回对应错误信息。
- 每次登录都要刷新token信息,
- 不能在用户访问的过程中token过期,只要用户访问,token就要刷新有效期。
- 如果token正确解析token中的用户id,根据用户id查询用户信息。
总的来说大致分为4步:
1定义拦截器--->2创建拦截器链配置类--->3配置拦截器链顺序--->4配置拦截排除项
首先,需要定义第一个拦截器类,该拦截器类需要实现 Spring 框架提供的 HandlerInterceptor 接口。该拦截器只做一件事就是刷新token。
import cn.hutool.json.JSONUtil; import com.mijiu.commom.util.JwtUtils; import com.mijiu.commom.util.UserHolder; import com.mijiu.entity.User; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @author mijiupro */ @Slf4j @Component public class RefreshTokenInterceptor implements HandlerInterceptor { private final JwtUtils jwtUtils; private final StringRedisTemplate stringRedisTemplate; public RefreshTokenInterceptor(JwtUtils jwtUtils, StringRedisTemplate stringRedisTemplate) { this.jwtUtils = jwtUtils; this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1、从请求头中获取token String authorizationHeader = request.getHeader("authorization"); if (StringUtils.isBlank(authorizationHeader)) { return true; } // 2.解析token Claims claims = jwtUtils.parseToken(authorizationHeader); if (Objects.isNull(claims)) { return true; } // 3.获取用户信息 Integer userId = claims.get("userId", Integer.class); String userInfoJson = stringRedisTemplate.opsForValue().get("login:user:" + userId); if (StringUtils.isBlank(userInfoJson)) { return true; } // 4.刷新token String refreshToken = jwtUtils.refreshToken(authorizationHeader); response.setHeader("Access-Control-Expose-Headers", "Authorization"); response.addHeader("Authorization", refreshToken); stringRedisTemplate.expire("login:user:" + userId, 30, TimeUnit.MINUTES); // 5.将用户信息存入本地线程方便获取 User user = JSONUtil.toBean(userInfoJson, User.class); UserHolder.setInfoByToken(user); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // 清理本地线程 UserHolder.clear(); } }
值得注意:因为有些接口是不需要认证的比如你在商城,浏览商品,是不是不登录也可以浏览。不登录就没token,没token就直接放行(认证交给后续的认证拦截器),有token就直接刷新(不可能你登录了浏览了30分钟,突然下单然后告诉你token过期重新登录吧,所以登录后调用的每个接口都要走一遍token刷新)。最后请求处理完一定要清理一下本地线程,不然用户多的时候内存占用会很大。
然后,就要实现一个认证拦截器了,实现用户身份认证。
import com.mijiu.commom.util.UserHolder; import com.mijiu.entity.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Objects; /** * @author mijiupro */ @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = UserHolder.getInfoByToken(); if (Objects.isNull(user)) { response.setStatus(401); return false; } return true; } }
值得注意:在上个拦截器我们是做过解析token了并存在本地线程里面,所以只需要判断本地线程有没有即可。
创建一个配置类,用于配置拦截器链。在该配置类中,通过实现 WebMvcConfigurer 接口来添加拦截器,具体包括 addInterceptors 方法。
import com.mijiu.commom.interceptor.LoginInterceptor; import com.mijiu.commom.interceptor.RefreshTokenInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author mijiupro */ @Configuration public class WebConfig implements WebMvcConfigurer { private final RefreshTokenInterceptor refreshTokenInterceptor; private final LoginInterceptor loginInterceptor; public WebConfig(RefreshTokenInterceptor refreshTokenInterceptor, LoginInterceptor loginInterceptor) { this.refreshTokenInterceptor = refreshTokenInterceptor; this.loginInterceptor = loginInterceptor; } @Override public void addInterceptors( InterceptorRegistry registry) { registry.addInterceptor(refreshTokenInterceptor) .addPathPatterns("/**").order(0);//设置拦截器对所有路径生效,执行顺序为0 registry.addInterceptor(loginInterceptor) .excludePathPatterns("/captcha/graph-captcha")//排除用户登录获取验证码接口 .excludePathPatterns("/","*/login","*.html","/images/**","/doc.html" ,"/webjars/**","/swagger-resources","/swagger-resources/**","/v3/**")//排除登录获取静态资源、swagger接口文档等。 .order(1);//设置拦截器对所有路径生效,执行顺序为1 } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 对所有路径生效 .allowedOrigins("*") //允许所有源地址 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法 .allowedHeaders("*"); // 允许的请求头 } }
刷新token的拦截器要最先执行,接着才是认证拦截器
像用户登录的验证码接口、登录接口以及像一些静态资源、网页、图片等需要进行拦截排除,如果整合了swagger接口文档也是需要排除的。
token拦截器链的设计与实现核心就四步:
1定义拦截器--->2创建拦截器链配置类--->3配置拦截器链顺序--->4配置拦截排除项
希望本文对你有帮助。