JWT+双token实现无感刷新

news2024/9/25 19:24:47

概述

使用 JWT + 双 Token 的无感刷新机制,主要是通过短期的 Access Token 和长期的 Refresh Token 相结合,保证系统的安全性和用户体验。其核心思想是:当 Access Token 过期时,使用 Refresh Token 无需用户再次登录即可获取新的 Access Token,从而实现无感刷新。

实现思路

  • Access Token:短期有效的 Token,主要用于鉴权,每次请求时都需要携带,过期时间较短,通常为几分钟到数小时。
  • Refresh Token:长期有效的 Token,仅用于刷新 Access Token,有效期较长,通常为几天到几周,不应该频繁使用,只有当 Access Token 过期时才使用。
  • 无感刷新:当 Access Token 过期时,客户端检测到 401 错误后,自动发送 Refresh Token 到服务器获取新的 Access Token,用户无感知。

实现步骤

添加依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成 Access Token 和 Refresh Token

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtTokenUtil {

    private static final String SECRET_KEY = "your_secret_key";  // 密钥,生产中应存储在安全地方
    private static final long ACCESS_TOKEN_EXPIRATION = 5 * 60 * 1000; // 5分钟
    private static final long REFRESH_TOKEN_EXPIRATION = 7 * 24 * 60 * 60 * 1000; // 7天

    // 生成 Access Token
    public static String generateAccessToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    // 生成 Refresh Token
    public static String generateRefreshToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    // 验证 Token 是否过期
    public static boolean isTokenExpired(String token) {
        try {
            Date expiration = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    // 从 Token 中获取用户名
    public static String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
    }
}

实现刷新 Token 逻辑

在用户登录时,后端会生成并返回 Access Token 和 Refresh Token。当 Access Token 过期时,客户端可以使用 Refresh Token 进行无感刷新。

@RestController
@RequestMapping("/auth")
public class AuthController {

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        // 验证用户登录逻辑
        String username = loginRequest.getUsername();
        
        // 生成 Access Token 和 Refresh Token
        String accessToken = JwtTokenUtil.generateAccessToken(username);
        String refreshToken = JwtTokenUtil.generateRefreshToken(username);

        // 返回给客户端
        Map<String, String> tokens = new HashMap<>();
        tokens.put("accessToken", accessToken);
        tokens.put("refreshToken", refreshToken);

        return ResponseEntity.ok(tokens);
    }

    @PostMapping("/refresh-token")
    public ResponseEntity<?> refreshAccessToken(@RequestBody RefreshTokenRequest refreshTokenRequest) {
        String refreshToken = refreshTokenRequest.getRefreshToken();

        // 验证 Refresh Token 是否有效
        if (JwtTokenUtil.isTokenExpired(refreshToken)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Refresh token expired");
        }

        // 通过 Refresh Token 获取用户名,并生成新的 Access Token
        String username = JwtTokenUtil.getUsernameFromToken(refreshToken);
        String newAccessToken = JwtTokenUtil.generateAccessToken(username);

        Map<String, String> tokens = new HashMap<>();
        tokens.put("accessToken", newAccessToken);

        return ResponseEntity.ok(tokens);
    }
}

前端实现无感刷新逻辑

前端在请求时需要携带 Access Token。当检测到 Access Token 过期时(如返回 401 错误),可以使用 Refresh Token 请求新的 Access Token,然后重试失败的请求。

import axios from 'axios';

// 获取 Token
let accessToken = localStorage.getItem('accessToken');
let refreshToken = localStorage.getItem('refreshToken');

// 创建 axios 实例
const api = axios.create({
  baseURL: 'http://localhost:8080', // 后端地址
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// 请求拦截器
api.interceptors.request.use(
  (config) => {
    config.headers['Authorization'] = `Bearer ${accessToken}`;
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
api.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        // 用 Refresh Token 获取新 Access Token
        const response = await axios.post('http://localhost:8080/auth/refresh-token', {
          refreshToken: refreshToken,
        });

        // 更新 Access Token
        accessToken = response.data.accessToken;
        localStorage.setItem('accessToken', accessToken);

        // 重新尝试原来的请求
        originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
        return api(originalRequest);
      } catch (refreshError) {
        // Refresh Token 过期或无效时需要重新登录
        console.error('Refresh token expired or invalid', refreshError);
        return Promise.reject(refreshError);
      }
    }
    return Promise.reject(error);
  }
);

// 示例 API 调用
api.get('/some-protected-resource')
  .then((response) => {
    console.log(response.data);
  })
  .catch((error) => {
    console.error(error);
  });

总结

通过 JWT + 双 Token 实现无感刷新,可以提高系统的安全性,并且提升用户体验,减少频繁登录的麻烦。在实现中:

  • Access Token 用于快速鉴权,有效期短;
  • Refresh Token 用于刷新 Access Token,有效期长,使用频率低;
  • 前端通过拦截响应中的 401 错误,实现无感刷新;

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

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

相关文章

KPaaS平台用户权限管理系统方案之表单设计统一单据制作与授权

不同的业务系统各自独立运行&#xff0c;需要分别进行授权操作&#xff0c;这不仅繁琐耗时&#xff0c;还容易出现错误和不一致的情况&#xff0c;导致企业在多系统用户权限角色管理中常常陷入困境&#xff0c;那么&#xff0c;有没有一种高效、便捷的解决方案呢&#xff1f; …

关于预处理详解,#define,宏的使用以及命名 函数与宏的区别详细对比

预定义符号 C语⾔设置了⼀些预定义符号&#xff0c;可以直接使⽤&#xff0c;预定义符号也是在预处理期间处理的 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI C&#xff…

汉诺塔的理解

数学思想——归纳推理&#xff08;不是反证法&#xff09; 为了方便&#xff0c;我把塔叫做牌&#xff0c;最左边的是从大到小&#xff08;底部开始&#xff09;放置的的牌堆。 数字的那一列是递归调用&#xff0c;右边长度不一的箭头是&#xff0c;数字阶段向下调用方法的情况…

稀土抗菌剂在涂料中应用的神奇表现

稀土抗菌剂的抗菌抑菌机理有四个层面:一是稀土化合物与细菌表面静电结合&#xff0c;造成直接的杀灭二是基于稀土的光催化半导体特性&#xff0c;通过光生氧自由基ROS机理杀灭细菌;三是稀土化合物破坏细胞膜通透性&#xff0c;造成破损导致细胞质流出杀灭细菌;四是稀土离子跨膜…

C标准库<string.h>-str、strn开头的函数

char *strcat(char *dest, const char *src) 函数功能 strcat 函数用于将一个字符串追加到另一个字符串的尾部。 参数解释 dest&#xff1a;指向目标字符串的指针&#xff0c;这个字符串的尾部将被追加 src 字符串的内容。src&#xff1a;指向源字符串的指针&#xff0c;其…

最精简的VScode Verilog RTL开发环境搭建教程

【2024-9月更新】最精简的VScode Verilog RTL开发环境搭建教程 文章目录 【2024-9月更新】最精简的VScode Verilog RTL开发环境搭建教程一、官网下载VScode二、登录账号同步三、安装配置拓展插件1.Verilog-HDL/systemVerilog拓展2.安装Universal Ctags● Windows系统安装univer…

(附源码) Springboot 飞速物流管理平台78584

摘要 受疫情的影响&#xff0c;很多城市处于静默的状态&#xff0c;导致店铺很多店铺都处于关闭的状态&#xff0c;给商家带来了极大的损失&#xff0c;很多商家为了减少损失都通过线上进行销售&#xff0c;比如直播、微商等&#xff1b;同时对于消费者来说&#xff0c;网上购买…

【Redis】分布式锁之 Redission

一、基于setnx实现的分布式锁问题 重入问题&#xff1a;获得锁的线程应能再次进入相同锁的代码块&#xff0c;可重入锁能防止死锁。例如在HashTable中&#xff0c;方法用synchronized修饰&#xff0c;若在一个方法内调用另一个方法&#xff0c;不可重入会导致死锁。而synchroni…

mysql练习题使用的表

dept(部门表):部门编号&#xff0c;部门名字&#xff0c;部门地点 salgrode工资等级表&#xff1a;等级&#xff0c;最高工资&#xff0c;最低工资 emp表&#xff1a;员工编号&#xff0c;员工名字&#xff0c;工作&#xff0c;领导编号MGR&#xff0c;入职时间&#xff0c;工…

Spring Boot 整合MyBatis-Plus 实现多层次树结构的异步加载功能

文章目录 1&#xff0c;前言2&#xff0c;什么是多层次树结构&#xff1f;3&#xff0c;异步加载的意义4&#xff0c;技术选型与实现思路5&#xff0c;具体案例5.1&#xff0c;项目结构5.2&#xff0c;项目配置&#xff08;pom.xml&#xff09;5.3&#xff0c;配置文件&#xf…

c++难点核心笔记(二)

系列文章目录 c难点&核心笔记(一) 继续接着上一章记录的重点内容包括函数&#xff0c;类和对象&#xff0c;指针和引用&#xff0c;C对象模型和this指针等内容&#xff0c;继续给大家分享&#xff01;&#xff01; 文章目录 系列文章目录友元全局函数做友元类做友元成员函…

国庆节怎么利用PHP发送文字短信

国庆节作为中国重要的法定节假日之一&#xff0c;不仅是全民欢庆的时刻&#xff0c;也是商家们进行促销活动的黄金时期。发送营销短信成为许多商家吸引顾客、提高销量的重要手段。 支持免费对接试用乐讯通PaaS平台 找好用的短信平台,选择乐讯通,短信群发|短信平台|群发短信软件…

分布式事务(1)

1.分布式事务 首先我们看看项目中的下单业务整体流程&#xff1a; 由于订单、购物车、商品分别在三个不同的微服务&#xff0c;而每个微服务都有自己独立的数据库&#xff0c;因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务&#xff1a; 交易服…

Python办公自动化教程(002):PDF的拆分与合并

1、PyPDF2 介绍 介绍&#xff1a; PyPDF2是一个用于处理PDF文件的Python库&#xff0c;它提供了丰富的功能来读取、编辑、合并、拆分PDF文档&#xff0c;以及提取文本、图像和其他内容。 功能&#xff1a; 读取PDF&#xff1a;PyPDF2可以轻松地打开和读取PDF文件&#xff0c;获…

pytorch 神经网络模型 2D+3D 可视化,这个工具库够猛!

生信碱移 torch模块可视化 小编近期冲浪的时候发现一个torch模型架构可视化的神级python库VisualTorch&#xff0c;给各位铁子分享一下doge。 VisualTorch旨在帮助可视化基于Torch的神经网络架构&#xff0c;似乎是今年才上传到github上。它目前支持为PyTorch的Sequential和Cu…

jQuery——jQuery的2把利器

1、jQuery 核心函数 ① 简称&#xff1a;jQuery 函数&#xff0c;即为 $ 或者 jQuery ② jQuery 库向外直接暴露的是 $ 或者 jQuery ③ 引入 jQuery 库后&#xff0c;直接使用 $ 即可 当函数用&#xff1a;$&#xff08;xxx&#xff09; 当对象用&#xff1a;$.xxx&#x…

华为官宣,不支持安卓应用的纯血鸿蒙终于来了

华为前不久与苹果新品发布会撞车的全球首款量产三折叠屏幕手机 Mate XT&#xff0c;本以为已是其下半年狠活儿担当。 但直到看完昨天下午的华为秋季全场景发布会才发现&#xff0c;好家伙&#xff0c;此前那都叫小打小闹&#xff0c;原来大招全搁在后头呢&#xff01; 这场近两…

蒙语学习快速方法,速记蒙语单词怎么学习更高效!

要高效学习蒙古语和速记单词&#xff0c;首先要掌握基础知识&#xff0c;如字母表和发音规则。接着&#xff0c;专注于学习日常用语和基础词汇&#xff0c;并运用记忆技巧如联想、发音和构词法来帮助记忆。利用专门的学习软件&#xff0c;如“蒙语学习通”&#xff0c;可以提供…

进程间通信 (一)【管道通信(上)】

目录 1. 概况2. 管道通信的原理2.1 初步理解2.2 深入理解 1. 概况 是什么&#xff1a;两个及以上的进程实现数据层面的交互&#xff0c;称为进程间的通信。 因为进程独立性的存在&#xff0c;所以一个进程无法直接访问另一个进程的数据&#xff0c;即便是父子进程&#xff0c;子…

数字IC设计\FPGA 职位经典笔试面试整理--基础篇2

1. 卡诺图 逻辑函数表达式可以使用其最小项相加来表示&#xff0c;用所有的最小项可以转换为卡诺图进行逻辑项化简 卡诺图讲解资料1 卡诺图讲解资料2 卡诺图讲解资料3 最小项的定义 一个函数的某个乘积项包含了函数的全部变量&#xff0c;其中每个变量都以原变量或反变量的形…