双 Token 无感刷新机制实现

✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。
🍎个人主页:Hhzzy99
🍊个人信条:坚持就是胜利!
💞当前专栏:项目实践
🥭本文内容:实现双Token的无感刷新。

双 Token 无感刷新机制实现

      • 后端依赖
      • 安全配置
      • Jwt过滤器 *
      • 前端的配置

在现代 Web 应用开发中,前后端分离已经成为一种趋势。Vue.js 作为前端框架,Java
作为后端语言的组合被广泛应用。在用户认证方面,JWT因为其无状态、易于扩展等特点也备受青睐。本文将详细介绍如何在 Vue 前端和 Java
后端实现双 Token 的无感刷新机制。

后端依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- 其他依赖 -->
</dependencies>

安全配置

配置Jwt过滤器,以及认证失败过滤器。

@Configuration
@EnableWebSecurity
public class SecurityConfig {/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** Jwt过滤器*/@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf().disable().cors().and().addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).headers().cacheControl().disable().and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/login", "/user/forgetPassword/**", "/user/sendUpdatePasswordEmailCode/**", "/user/register", "/swagger-ui.html", "/user/sendEmailLoginCode", "/user/verifyEmailLoginCode/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().headers().frameOptions().disable();return http.build();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}// 使用BCryptPasswordEncoder作为security默认的passwordEncoder@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

Jwt过滤器 *

这个过滤器是实现token刷新机制的核心,每次前端的请求携带accessToken与refreshToken过来,此过滤器拿到之后,先对accessToken进行解析,如果解析失败(过期),那么接下来会对refreshToken进行解析,解析完成之后,如果没有过期,就会生成新的accessToken与refreshToken返回给前端,并且设置一个新的请求头Token-Refreshed,值可以随便设,前端能拿到就好。

package com.hblog.backend.config;import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hblog.backend.entity.LoginUser;
import com.hblog.backend.entity.User;
import com.hblog.backend.exception.BusinessException;
import com.hblog.backend.exception.EnumException;
import com.hblog.backend.mapper.IUserMapper;
import com.hblog.backend.response.CommonResponse;
import com.hblog.backend.utli.*;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;/*** @ClassName: JwtAuthenticationFilter* @author: Hhzzy99* @date: 2024/3/17 16:09* description:继承每个请求只会经过一次的过滤器*/
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Value("${token.expiration}")private Long expiration;@Autowiredprivate IUserMapper userMapper;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取当前请求路径String requestPath = request.getRequestURI();// 排除不需要过滤的路径if (requestPath.equals("/user/login") || requestPath.equals("/user/register")) {filterChain.doFilter(request, response);return;}// 获取tokenString accessToken = request.getHeader("access_token");String refreshToken = request.getHeader("refresh_token");if ("null".equals(accessToken) || "".equals(accessToken) || "undefined".equals(accessToken) || null == accessToken) {// 放行filterChain.doFilter(request, response);return;}// 解析tokenString userId = "";boolean isRefresh = false;try {userId = JwtUtils.parseJWT(accessToken).getSubject();} catch (Exception e) {isRefresh = true;e.printStackTrace();}if (isRefresh) {try {userId = JwtUtils.parseJWT(refreshToken).getSubject();accessToken = JwtUtils.createJWT(userId);refreshToken = JwtUtils.createRefreshToken(userId);User loginUser = userMapper.getUserById(Long.valueOf(userId));Integer ttl = expiration.intValue() / 1000;log.warn("@@@@@@@@@@@@@@@@@@刷新token@@@@@@@@@@@@@@@@@@@@@");redisCache.setCacheObject("userInfo:" + userId, loginUser, ttl, TimeUnit.SECONDS);writeTokenResponse(response, accessToken, refreshToken);return;} catch (Exception e1) {throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED);}}// 从redis里面获取用户信息User loginUser = redisCache.getCacheObject("userInfo:" + userId);if (Objects.isNull(loginUser)) {throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED);}// 存入SecurityContextHolderUsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);// 放行filterChain.doFilter(request, response);}private void writeTokenResponse(HttpServletResponse response, String accessToken, String refreshToken) throws IOException {Map<String, String> tokenMap = new HashMap<>();tokenMap.put("accessToken", accessToken);tokenMap.put("refreshToken", refreshToken);Map<String, String> headers = new HashMap<>();headers.put("Token-Refreshed", "true");CommonResponse<Map<String, String>> commonResponse = new CommonResponse<>(200, "Token refreshed successfully", tokenMap);WebUtil.renderString(response, headers, JSON.toJSONString(commonResponse));}
}

前端的配置

前端对所有的axios请求进行全局配置,先在每次请求的时候设置好请求头accessToken与refreshToken,并且将每次请求都保存起来,如果在请求时后端解析到accessToken失效,并且返回了新的accessToken与refreshToken,在请求头拿到了后端设置好的Token-Refreshed,此时就可以重新将新的accessToken与refreshToken保存在浏览器本地,并且重新发送之前保存好的请求,就可以实现无感刷新。

request.js

import axios from 'axios'
import {ref} from "vue";// create an axios instance
const service = axios.create({baseURL: '/api', // url = base url + request urltimeout: 20000 // request timeout
})const retryRequest = ref(null)// request interceptor
service.interceptors.request.use(config => {// 加入头信息配置if (localStorage.getItem("access_token") !== null && localStorage.getItem("access_token") !== undefined){config.headers['access_token'] = localStorage.getItem("access_token")}if (localStorage.getItem("refresh_token") !== null && localStorage.getItem("refresh_token") !== undefined){config.headers['refresh_token'] = localStorage.getItem("refresh_token")}retryRequest.value = configreturn config}
)// response interceptor
service.interceptors.response.use(response => {if (response.headers['token-refreshed']) {console.log('Token刷新成功');// 如果有Token-Refreshed头部,更新本地存储中的TokenlocalStorage.setItem('access_token', response.data.data.accessToken);localStorage.setItem('refresh_token', response.data.data.refreshToken);console.log("继续")// 继续发送原始请求return axios(retryRequest.value)}return response;},async error => {const originalRequest = error.config;// 如果是Token过期导致的401错误,并且没有retry标记,尝试刷新Tokenif (error.response.status === 401 && !originalRequest._retry) {originalRequest._retry = true;const refreshToken = localStorage.getItem('refresh_token');if (refreshToken) {try {const response = await axios.post('/refresh-token', { refreshToken });const { accessToken, refreshToken: newRefreshToken } = response.data.data;localStorage.setItem('access_token', accessToken);localStorage.setItem('refresh_token', newRefreshToken);// 更新原始请求的Authorization头部originalRequest.headers['access_token'] = accessToken;// 重新发送原始请求return instance(originalRequest);} catch (refreshError) {// 刷新Token失败,跳转到登录页或执行其他处理console.error('Token刷新失败:', refreshError);// 这里可以跳转到登录页或者执行其他处理}}}return Promise.reject(error);}
)export default service

通过以上步骤,我们就可以实现双 Token 无感刷新机制。该机制通过短期有效的访问 Token 和长期有效的刷新 Token 相结合,在 Token 过期时自动刷新。

本示例仅展示了基础的实现方式,实际生产环境中还需要考虑更多安全性和健壮性的问题。

希望这篇文章能帮助你更好地理解和实现 JWT 双 Token 无感刷新机制。如有任何问题或建议,欢迎在评论区留言讨论。

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

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

相关文章

密室逃脱——收集版修改测试

一、原版修改 1、导入资源 Unity Learn | 3D Beginner: Complete Project | URP 2、设置Scene 删除SampleScene&#xff0c;打开UnityTechnologies-3DBeginnerComplete下的MainScene 3、降低音量 (1) 打开Hierarchy面板上的Audio降低音量 (2) 打开Prefabs文件夹&#xf…

lnmp php7 安装ssh2扩展

安装ssh2扩展前必须安装libssh2包 下载地址: wget http://www.libssh2.org/download/libssh2-1.11.0.tar.gzwget http://pecl.php.net/get/ssh2-1.4.tgz &#xff08;这里要换成最新的版本&#xff09; 先安装 libssh2 再安装 SSH2: tar -zxvf libssh2-1.11.0.tar.gzcd libss…

【日志信息管理】管理日志信息的类

日志用于记录程序的执行记录包括程序的出错记录&#xff0c;程序致命退出原因&#xff0c;程序的正常执行记录。这样我们就可以很快的察觉程序的错误原因、执行状况等等&#xff0c;因此管理日志信息是非常重要的。 日志一般由以下部分组合&#xff1a; 日志时间、日志等级、…

Java 基础--File - IO流(2)

I/O流 定义 数据从硬盘流向内存为输入流&#xff0c;数据从内存流向硬盘为输出流。输入也叫读取数据&#xff0c;输出也叫写出数据。 IO分类 1.按照数据的流向分为&#xff1a;输入流和输出流 ①输入流&#xff1a;把数据从其他设备上读取到内存中的流 ②输出流&#xff1…

Qt 基础组件速学 事件过滤器

学习目标&#xff1a;理解事件过滤器 前置环境 运行环境:qt creator 4.12 学习内容和效果演示&#xff1a; Qt 提供了事件过滤器的机制,允许我们在事件到达目标对象之前对事件进行拦截和处理。这在以下情况下非常有用: 全局事件处理: 我们可以在应用程序级别安装一个事件过…

工控人最爱的PLC触摸屏一体机,有多香

PLC触摸屏一体机是什么 PLC触摸屏一体机&#xff0c;听起来可能有点技术化&#xff0c;但简单来说&#xff0c;它就是一个集成了可编程逻辑控制器&#xff08;PLC&#xff09;和触摸屏的智能设备。这种设备不仅能够执行自动化控制任务&#xff0c;还能实时显示和操作设备状态&a…

JVM原理(十九):JVM虚拟机内存模型

1. 硬件的效率与一致性 数据不安全的原因&#xff1a;缓存一致性的问题 共享内存多核系统&#xff1a;在多路处理器系统中&#xff0c;每个处理器都有自己的高速缓存&#xff0c;而他们又共享同一主内存。 线程先后执行结果不一致问题&#xff1a;除了增加高速缓存之外&#…

【Python】已解决:nltk.download(‘stopwords‘) 报错问题

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;nltk.download(‘stopwords’) 报错问题 一、分析问题背景 在使用Python的自然语言处理库NLTK&#xff08;Natural Language Toolkit&#xff09;时&#xff0c…

CentOS 7安装Elasticsearch7.7.0和Kibana

一. 准备安装包 elasticsearch和kibana&#xff1a;官网历史版本找到并下载&#xff08;https://www.elastic.co/cn/downloads/past-releases#elasticsearch&#xff09;ik分词器&#xff1a;GitHub下载&#xff08;https://github.com/infinilabs/analysis-ik/releases/tag/v…

设计模式之状态机模式

一、状态机模式介绍 状态机模式&#xff08;State Machine Pattern&#xff09;是一种用于描述对象行为的软件设计模式&#xff0c;属于行为型设计模式。在状态机模式中&#xff0c;对象的行为取决于其内部状态&#xff0c;并且在不同的状态下&#xff0c;对象可能会有不同的行…

STM32F103C8T6核心板原理图和PCB分享

PCB图 原理图 资料下载地址&#xff1a; 原理图PCB库: https://545c.com/d/45573183-61875742-29897c?p7526 (访问密码: 7526)

[go-zero] 简单微服务调用

文章目录 1.注意事项2.服务划分及创建2.1 用户微服务2.2 订单微服务 3.启动服务3.1 etcd 服务启动3.2 微服务启动3.3 测试访问 1.注意事项 go-zero微服务的注册中心默认使用的是Etcd。 本小节将以一个订单服务调用用户服务来简单演示一下&#xff0c;其实订单服务是api服务&a…

Java 使用sql查询mongodb

在现代应用开发中&#xff0c;关系型数据库和NoSQL数据库各有千秋。MongoDB作为一种流行的NoSQL数据库&#xff0c;以其灵活的文档模型和强大的扩展能力&#xff0c;受到广泛欢迎。然而&#xff0c;有时开发者可能更熟悉SQL查询语法&#xff0c;或者需要在现有系统中复用SQL查询…

【ARMv8/v9 GIC 系列 5.6 -- GIC 超优先级中断详细介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 Interrupt superpriority超优先级中断的特性和应用Physical interface interrupt signalsPhysical Group 1 Non-NMI for Current Security StatePhysical Group 1 for Other Security State, or a Group 0 Non-NMIPhysical Group 1 …

进程控制-wait和waitpid进程回收

wait 阻塞函数 函数作用&#xff1a; 1. 阻塞并等待子进程退出 2. 回收子进程残留资源 3. 获取子进程结束状态&#xff08;退出原因&#xff09; pid_t wait(int *wstatus); 返回值&#xff1a; ‐1 : 回收失败&#xff0c;已经没有子进程了 >0 : 回收子进程对应的…

一种非凸全变差正则化的信号降噪方法(以模拟信号和轴承振动信号为例,MATLAB)

以旋转机械振动信号为例&#xff0c;由于旋转机械运行中背景噪声较强&#xff0c;振动信号需要进行降噪处理。常用的小波阈值降噪会在信号的不连续处产生虚假的波峰和伪吉布森震荡&#xff0c;而奇异值分解SVD去噪容易产生虚假分量&#xff0c;全变差去噪则不会出现这样的情况&…

深入理解JS逆向代理与环境监测

博客文章&#xff1a;深入理解JS逆向代理与环境监测 1. 引言 首先要明确JavaScript&#xff08;JS&#xff09;在真实网页浏览器环境和Node.js环境中有很多使用特性的区别。尤其是在环境监测和对象原型链的检测方面。本文将探讨如何使用JS的代理&#xff08;Proxy&#xff09…

STM32-USART

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. 串口通信协议1.1 通信接口1.2 串口通信1.3 硬件电路1.4 电平标准1.5 串口参数及时序1.6 串口时序 2. USART串口通信2.1 USART简介2.2 USART框图2.3 USART基本结构2.4 数据帧2.5 数据帧-配置停止位2.6 起始位侦测2.…

【Python】一文向您详细介绍 argparse中 action=‘store_true’ 的作用

【Python】一文向您详细介绍 argparse中 action‘store_true’ 的作用 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;98…

pdf怎么转换成图片格式文件,pdf文档怎么转换成图片格式

在数字化时代&#xff0c;pdf文件转换成图片格式是一种常见的操作&#xff0c;无论是在工作还是日常生活中&#xff0c;我们总会遇到需要将pdf文件转换为图片的需求。这可能是因为图片格式更易于分享、展示或编辑。那么&#xff0c;如何高效地将pdf转换成图片呢&#xff1f;本文…