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

news2025/1/19 18:31:14

 

⛰️个人主页:     蒾酒

🔥系列专栏:《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配置拦截排除项

1.定义拦截器

首先,需要定义第一个拦截器类,该拦截器类需要实现 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 false;
    }

    @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了并存在本地线程里面,所以只需要判断本地线程有没有即可。

2.创建拦截器链配置类

创建一个配置类,用于配置拦截器链。在该配置类中,通过实现 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("*"); // 允许的请求头
    }
}

3.配置拦截器链顺序

刷新token的拦截器要最先执行,接着才是认证拦截器

4.配置拦截排除项

像用户登录的验证码接口、登录接口以及像一些静态资源、网页、图片等需要进行拦截排除,如果整合了swagger接口文档也是需要排除的。

最后

token拦截器链的设计与实现核心就四步:

1定义拦截器--->2创建拦截器链配置类--->3配置拦截器链顺序--->4配置拦截排除项

希望本文对你有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1496193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI反击Elon Musk

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

Ribbon实现Cloud负载均衡

安装Zookeeper要先安装JDK环境 解压 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /usr/local/develop 配置JAVA_HOME vim /etc/profile export JAVA_HOME/usr/local/develop/jdk1.8.0_191 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOM…

力扣515. 在每个树行中找最大值(BFS,DFS)

Problem: 515. 在每个树行中找最大值 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1:BFS 套用BFS模板,直接在遍历树的某一层时将当前层的最大值存入数组中 思路2:DFS 回溯思想,在递归时不断更新可选列表(根据…

【word】引用文献如何标注右上角

一、在Word文档中引用文献并标注在右上角的具体步骤如下 1、将光标移动到需要添加文献标注的位置: 2、在文档上方的工具栏中选择“引用”选项: 3、点击“插入脚注”或“插入尾注”: ①如果选择的是脚注,则脚注区域会出现在本页的…

git:git revert 和git reset 回退版本的使用方式

目录 git revert还原某些现有提交git reset删除提交参考 git revert还原某些现有提交 中文文档:https://git-scm.com/docs/git-revert/zh_HANS-CN 版本会递增,不影响之前提交的内容 例如:撤销记录为 abc123 的提交 git revert abc123git r…

企微hook源码

企微hook源码已经在QQ群内开源。速度进群下载,避免和谐。 QQ群:649480745

【sgPhotoPlayer】自定义组件:图片预览,支持点击放大、缩小、旋转图片

特性&#xff1a; 支持设置初始索引值支持显示标题、日期、大小、当前图片位置支持无限循环切换轮播支持鼠标滑轮滚动、左右键、上下键、PageUp、PageDown、Home、End操作切换图片支持Esc关闭窗口 sgPhotoPlayer源码 <template><div :class"$options.name"…

【JS】关于this的使用

this 前言一、this是什么&#xff1f;二、做什么&#xff1f;1.全局环境2.函数环境3.new实例对象4.apply、bind、call绑定4.1 apply()4.2 call()4.3 bind() 三、为什么用this&#xff1f;四、如何改变this&#xff1f;五、应用场景&#xff1f;总结 前言 痛点 经常写Vue项目&a…

springboot集成logback打印彩色日志

一、logback介绍 Logback是由log4j创始人设计的另一个开源日志组件,官方网站&#xff1a; logback.qos.ch。它当前分为以下三个模块&#xff1a; logback-core&#xff1a;其它两个模块的基础模块。logback-classic&#xff1a;它是log4j的一个改良版本&#xff0c;同时它完整实…

qt练习案例

记录一下qt练习案例&#xff0c;方便学习qt知识点 基本部件 案例1 需求&#xff0c;做一个标签&#xff0c;显示"你好"知识点&#xff0c;QLabel画面 4. 参考&#xff0c;Qt 之 QLabel 案例2 需求&#xff0c;做一个标签&#xff0c;显示图片 知识点&#xff0c;…

【linux进程信号(二)】信号的保存,处理以及捕捉

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程信号 1. 前言2. 信号阻塞…

仅通过头部便可控制机器人实现生活自理!四肢瘫患者福音真的来了?

运动功能障碍对个体执行基本日常生活活动&#xff08;如沐浴、更衣、用餐&#xff09;以及进行工具性日常生活活动&#xff08;包括娱乐和社交互动&#xff09;造成了显著影响&#xff0c;不仅限制了他们的活动范围&#xff0c;而且削弱了他们维持独立生活的基础。受此类障碍困…

Netty架构

Netty逻辑架构 Netty 的逻辑处理架构为典型网络分层架构设计&#xff0c;网络通信层、事件调度层、服务编排层。 一、 网络通信层 网络通信层的职责是执行网络 I/O 的操作。它支持多种网络协议和 I/O 模型的连接操作。当网络数据读取到内核缓冲区后&#xff0c;会触发网络事件…

还在用Jenkins?试试这款阿里出品的IDEA插件,部署项目贼香

CloudToolkit简介 CloudToolkit是阿里出品的一款IDEA插件,通过它我们可以更方便地实现自动化部署,其内置的终端工具和文件上传功能,即使用来管理服务器也非常方便!这款IDEA插件不仅功能强大,而且完全免费! 安装 CloudToolkit的安装是非常简单的,直接在IDEA的插件市场…

如何在Vue中实现拖拽功能?

Vue.js是一款流行的JavaScript框架&#xff0c;用于构建用户界面。其中一个常见的需求是在Vue中实现拖拽功能&#xff0c;让用户可以通过拖拽元素来进行交互。今天&#xff0c;我们就来学习如何在Vue中实现这一功能。 首先&#xff0c;我们需要明白拖拽功能的基本原理&#xf…

yduibuilder,拖拽式开发轻松高效自动生成前端代码

给大家分享一个#开源项目# &#xff1a;#yduibuilder# &#xff0c;他可以通过拖拽式的开发方式自动生成前端代码&#xff0c;这种#低代码开发工具# 已经很多了&#xff0c;没什么新鲜的&#xff1b; 但yduibuilder式通过编译的方式#生成终端代码# &#xff0c;没有预设各种功能…

2.2 评估方法 机器学习

我们若有一个包含m个样例的数据集&#xff0c;若我们既需要训练&#xff0c;也需要测试&#xff0c;我们该如何处理呢&#xff1f;下面是几种方法&#xff1a; 2.2.1 留出法 “留出法”直接将数据集D划分为两个互斥的集合&#xff0c;其中一个作为训练集S&#xff0c;另一个作…

[数据结构初阶】栈

各位读者老爷好&#xff0c;鼠鼠我好久没写博客了&#xff08;太摆烂了&#xff09;&#xff0c;今天就基于C语言浅介绍一下数据结构里面的栈&#xff0c;希望对你有所帮助吧。 目录 1.栈的概念及结构 2.栈的实现 2.1定义栈 2.2.初始化栈 2.3.入栈 2.4.出栈 2.5.获取栈…

小白跟做江科大51单片机之LCD1602滚动显示效果

1.查看原理图 图1 LCD1602接口 图2 LCD1602与STC的接口 2.编写代码 图3 时序结构 根据时序结构编写命令和写入数据代码 #include <REGX52.H> #include "Delay.h" sbit LCD1602_ENP2^7; sbit LCD1602_RSP2^6; sbit LCD1602_WRP2^5; #define LCD1602_lCD0 …

Dgraph 入门教程二《 快速开始》

1、Clound 云 云地址&#xff1a;Dgraph Cloud 登录Clound 云后&#xff0c;可以用云上的东西操作&#xff0c;可以用谷歌账号或者github账号登录。 启动云 &#xff08;1&#xff09;在云控制台&#xff0c;点击 Launch new backend. &#xff08;2&#xff09;选择计划&…