spring boot3token拦截器链的设计与实现

spring boot3token拦截器链的设计与实现,第1张

<div class="htmledit_views" id="content_views">
<p> <img alt="" height="68" src="https://img-blog.csdnimg.cn/direct/a31da70afc294501ba5d6e07af20bd59.jpeg" width="68"/></p>
<p>⛰️个人主页:     <a href="https://blog.csdn.net/qq_62262918?spm=1011.2266.3001.5343" title="蒾酒">蒾酒</a></p>
<p>系列专栏:<a href="https://blog.csdn.net/qq_62262918/category_12571219.html?spm=1001.2014.3001.5482" title="《spring boot实战》">《spring boot实战》</a></p>
<p><span style="color:#a2e043;">山高路远,行路漫漫,终有归途。</span></p>
<hr/>
<p id="main-toc"><strong>目录</strong></p>
<p id="1.%E5%89%8D%E8%A8%80-toc" style="margin-left:0px;"><a href="#1.%E5%89%8D%E8%A8%80" rel="nofollow">写在前面</a></p>
<p id="%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90-toc" style="margin-left:0px;"><a href="#%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90" rel="nofollow">流程分析</a></p>
<p id="%E9%9C%80%E8%A6%81%E6%B8%85%E6%A5%9A%E7%9A%84-toc" style="margin-left:0px;"><a href="#%E9%9C%80%E8%A6%81%E6%B8%85%E6%A5%9A%E7%9A%84" rel="nofollow">需要清楚的</a></p>
<p id="%E5%AE%9E%E7%8E%B0%E6%AD%A5%E9%AA%A4-toc" style="margin-left:0px;"><a href="#%E5%AE%9E%E7%8E%B0%E6%AD%A5%E9%AA%A4" rel="nofollow">实现步骤</a></p>
<p id="1.%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8-toc" style="margin-left:40px;"><a href="#1.%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8" rel="nofollow">1.定义拦截器</a></p>
<p id="2.%E5%88%9B%E5%BB%BA%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%85%8D%E7%BD%AE%E7%B1%BB-toc" style="margin-left:40px;"><a href="#2.%E5%88%9B%E5%BB%BA%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%85%8D%E7%BD%AE%E7%B1%BB" rel="nofollow">2.创建拦截器链配置类</a></p>
<p id="3.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%A1%BA%E5%BA%8F-toc" style="margin-left:40px;"><a href="#3.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%A1%BA%E5%BA%8F" rel="nofollow">3.配置拦截器链顺序</a></p>
<p id="4.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E6%8E%92%E9%99%A4%E9%A1%B9-toc" style="margin-left:40px;"><a href="#4.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E6%8E%92%E9%99%A4%E9%A1%B9" rel="nofollow">4.配置拦截排除项</a></p>
<p id="%E6%9C%80%E5%90%8E-toc" style="margin-left:0px;"><a href="#%E6%9C%80%E5%90%8E" rel="nofollow">最后</a></p>
<hr id="hr-toc"/>
<p></p>
<h2>写在前面</h2>
<p>本文介绍了spring boot后端服务开发中有关如何设计拦截器的思路,坚持看完相信对你有帮助。</p>
<p>同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。</p>
<p></p>
<h2 id="%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90">流程分析</h2>
<p>用户在进行登陆后服务器会发放token等信息一起返回给前端,前端会进行保存,那么token里面是携带一些有关用户的身份等信息的,用户端在请求后端时需要在请求头携带token,请求先被拦截器截获,只有经过多重拦截器校验通过后才可以执行对应功能接口,否则会抛出异常返回对应错误信息。</p>
<p></p>
<p></p>
<h2 id="%E9%9C%80%E8%A6%81%E6%B8%85%E6%A5%9A%E7%9A%84">需要清楚的</h2>
<blockquote>
<ul><li>每次登录都要刷新token信息,</li><li>不能在用户访问的过程中token过期,只要用户访问,token就要刷新有效期。</li><li>如果token正确解析token中的用户id,根据用户id查询用户信息。</li></ul>
</blockquote>
<p></p>
<h2 id="%E5%AE%9E%E7%8E%B0%E6%AD%A5%E9%AA%A4">实现步骤</h2>
<p>总的来说大致分为4步:</p>
<blockquote>
<p><strong>1定义拦截器--->2创建拦截器链配置类--->3配置拦截器链顺序--->4配置拦截排除项</strong></p>
</blockquote>
<h3 id="1.%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8">1.定义拦截器</h3>
<p>首先,需要定义第一个拦截器类,该拦截器类需要实现 Spring 框架提供的 <p style='background-color:black;color:white;padding:12px;'>HandlerInterceptor</p> 接口。该拦截器<span style="background-color:#ffd900;">只做一件事就是刷新token。</span></p>
<p></p>
<pre><p style='background-color:black;color:white;padding:12px;'>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();
    }
}</p></pre>
<blockquote>
<p><span style="background-color:#ffd900;">值得注意:因为有些接口是不需要认证的比如你在商城,浏览商品,是不是不登录也可以浏览。不登录就没token,没token就直接放行(认证交给后续的认证拦截器),有token就直接刷新(不可能你登录了浏览了30分钟,突然下单然后告诉你token过期重新登录吧,所以登录后调用的每个接口都要走一遍token刷新)。最后请求处理完一定要清理一下本地线程,不然用户多的时候内存占用会很大。</span></p>
</blockquote>
<h3></h3>
<p>然后,就要实现一个认证拦截器了,实现用户身份认证。</p>
<pre><p style='background-color:black;color:white;padding:12px;'>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;
    }
}</p></pre>
<h3></h3>
<blockquote>
<p>值得注意:在上个拦截器我们是做过解析token了并存在本地线程里面,所以只需要判断本地线程有没有即可。</p>
</blockquote>
<h3 id="2.%E5%88%9B%E5%BB%BA%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%85%8D%E7%BD%AE%E7%B1%BB">2.<strong>创建拦截器链配置类</strong></h3>
<p>创建一个配置类,用于配置拦截器链。在该配置类中,通过实现 <p style='background-color:black;color:white;padding:12px;'>WebMvcConfigurer</p> 接口来添加拦截器,具体包括 <p style='background-color:black;color:white;padding:12px;'>addInterceptors</p> 方法。</p>
<pre><p style='background-color:black;color:white;padding:12px;'>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("*"); // 允许的请求头
    }
}</p></pre>
<h3 id="3.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E9%A1%BA%E5%BA%8F"><strong>3.配置拦截器链顺序</strong></h3>
<p>刷新token的拦截器要最先执行,接着才是认证拦截器</p>
<p><img alt="" height="79" src="https://img-blog.csdnimg.cn/direct/90993ecceff44c41ac34c1efe6f7e372.png" width="977"/></p>
<p><img alt="" height="203" src="https://img-blog.csdnimg.cn/direct/495616075e704bd5bb9fad0c9201f448.png" width="1200"/></p>
<p></p>
<h3 id="4.%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E6%8E%92%E9%99%A4%E9%A1%B9"><strong>4.配置拦截排除项</strong></h3>
<p>像用户登录的验证码接口、登录接口以及像一些静态资源、网页、图片等需要进行拦截排除,如果整合了swagger接口文档也是需要排除的。</p>
<p><img alt="" height="111" src="https://img-blog.csdnimg.cn/direct/cffc7643a9434d399bda88369c7aca3a.png" width="1200"/></p>
<p></p>
<h2 id="%E6%9C%80%E5%90%8E">最后</h2>
<p>token拦截器链的设计与实现核心就四步:</p>
<p><strong>1定义拦截器--->2创建拦截器链配置类--->3配置拦截器链顺序--->4配置拦截排除项</strong></p>
<p>希望本文对你有帮助。</p>
</div>

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/13518890.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024-04-11
下一篇 2024-04-24

发表评论

登录后才能评论

评论列表(0条)

保存