双token实现无感刷新

news2024/12/24 0:02:21
  • accessToken:用户获取数据权限
  • refreshToken:用来获取新的accessToken

双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。

引入依赖

<!--        JWT依赖-->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.47</version>
</dependency>

application.yml配置 

spring.redis.host=43.139.59.28
spring.redis.port=6379
spring.redis.timeout=10s
spring.redis.password=123
# 加密密钥
jwt.secret=zhangxb
# header 名称
jwt.header=Authorization
# accessToken有效单位为秒
jwt.expire.accessToken=3600
# refreshToken有效单位为秒
jwt.expire.refreshToken=30

JwtToken工具类

import com.alibaba.fastjson.JSONObject;
import com.example.demo.model.UserToken;
import com.example.demo.model.UserTokenInfo;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
 
/**
 * @author: zxb
 * @createTime: 2022-11-23 14:55
 * @version: 1.0.0
 * @Description: JwtToken 工具类
 */
@Component
public class JwtTokenUtil {

    /**
     * 获取黑名单前缀
     */
    public static final String TOKEN_BLACKLIST_PREFIX = "TOKEN_BLACKLIST-"; 

    @Value("${jwt.secret}")
    public String secret;
 
    @Value("${jwt.header}")
    public String header;
 
    @Value("${jwt.expire.accessToken}")
    public Integer accessTokenExpire;
 
    @Value("${jwt.expire.refreshToken}")
    public Integer refreshTokenExpire;
 
    @Resource
    RedisUtil redisUtil;
 
 
    /**
     * 保存 刷新令牌 与 访问令牌 关联关系 到redis
     *
     * @param userToken
     * @param refreshTokenExpireDate
     */
    public void tokenAssociation(UserToken userToken, Date refreshTokenExpireDate) {
        Long time = (refreshTokenExpireDate.getTime() - System.currentTimeMillis()) / 1000 + 100;
        redisUtil.set(userToken.getRefreshToken(), userToken.getAccessToken(), time);
    }
 
    /**
     * 根据 刷新令牌 获取 访问令牌
     *
     * @param refreshToken
     */
    public String getAccessTokenByRefresh(String refreshToken) {
        Object value = redisUtil.get(refreshToken);
        return value == null ? null : String.valueOf(value);
    }
 
 
    /**
     * 添加至黑名单
     *
     * @param token
     * @param expireTime
     */
    public void addBlacklist(String token, Date expireTime) {
        Long expireTimeLong = (expireTime.getTime() - System.currentTimeMillis()) / 1000 + 100;
        redisUtil.set(getBlacklistPrefix(token), "1", expireTimeLong);
    }
 
    /**
     * 校验是否存在黑名单
     *
     * @param token
     * @return true 存在 false不存在
     */
    public Boolean checkBlacklist(String token) {
        return redisUtil.hasKey(getBlacklistPrefix(token));
    }
 
    /**
     * 获取黑名单前缀
     *
     * @param token
     * @return
     */
    public String getBlacklistPrefix(String token) {
        return Constants.TOKEN_BLACKLIST_PREFIX + token;
    }
 
 
    /**
     * 生成双令牌
     *
     * @param userTokenInfo
     * @return
     */
    public UserToken createToekns(UserTokenInfo userTokenInfo) {
        Date nowDate = new Date();
        Date accessTokenExpireDate = new Date(nowDate.getTime() + accessTokenExpire * 1000);
        Date refreshTokenExpireDate = new Date(nowDate.getTime() + refreshTokenExpire * 1000);
        UserToken userToken = new UserToken();
        BeanUtils.copyProperties(userTokenInfo, userToken);
        userToken.setAccessToken(createToken(userTokenInfo, nowDate, accessTokenExpireDate));
        userToken.setRefreshToken(createToken(userTokenInfo, nowDate, refreshTokenExpireDate));
 
        // 创建 刷新令牌 与 访问令牌 关联关系
        tokenAssociation(userToken, refreshTokenExpireDate);
        return userToken;
    }
 
    /**
     * 生成token
     *
     * @param userTokenInfo
     * @return
     */
    public String createToken(UserTokenInfo userTokenInfo, Date nowDate, Date expireDate) {
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(JSONObject.toJSONString(userTokenInfo))
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
 
    /**
     * 获取 token 中注册信息
     *
     * @param token
     * @return
     */
    public Claims getTokenClaim(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
           claims=e.getClaims();
        }
        return claims;
    }
 
    /**
     * 验证 token 是否过期失效
     *
     * @param token
     * @return true 过期 false 未过期
     */
    public Boolean isTokenExpired(String token) {
        return getExpirationDate(token).before(new Date());
    }
 
    /**
     * 获取 token 失效时间
     *
     * @param token
     * @return
     */
    public Date getExpirationDate(String token) {
        return getTokenClaim(token).getExpiration();
    }
 
 
    /**
     * 获取 token 发布时间
     *
     * @param token
     * @return
     */
    public Date getIssuedAtDate(String token) {
        return getTokenClaim(token).getIssuedAt();
    }
 
 
    /**
     * 获取用户信息
     *
     * @param token
     * @return
     */
    public UserTokenInfo getUserInfoToken(String token) {
        String subject = getTokenClaim(token).getSubject();
        UserTokenInfo userTokenInfo = JSONObject.parseObject(subject, UserTokenInfo.class);
        return userTokenInfo;
    }
 
    /**
     * 获取用户名
     *
     * @param token
     * @return
     */
    public String getUserName(String token) {
        UserTokenInfo userInfoToken = getUserInfoToken(token);
        return userInfoToken.getUserName();
    }
 
    /**
     * 获取用户Id
     *
     * @param token
     * @return
     */
    public Long getUserId(String token) {
 
        UserTokenInfo userInfoToken = getUserInfoToken(token);
        return userInfoToken.getUserId();
    }
 
}

JwtFilter 拦截器

import com.example.demo.model.UserTokenInfo;
import com.example.demo.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * @author: zxb
 * @createTime: 2022-10-17 19:03
 * @version: 1.0.0
 * @Description: Jwt 拦截器
 */
@Slf4j
@Component
public class JwtFilter extends HandlerInterceptorAdapter {
 
    @Resource
    JwtTokenUtil jwtTokenUtil;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取token
        String token = request.getHeader(jwtTokenUtil.header);
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(jwtTokenUtil.header);
        }
 
        if (StringUtils.isEmpty(token)) {
            // 只是简单DEMO,这里直接返回false,可以自己进行添加
            log.error("token 不能为空!");
            return false;
        }
 
        // 判断token是否超时
        if (jwtTokenUtil.isTokenExpired(token)) {
            log.error("token 已失效!");
            return false;
        }
 
        // 判断 token 是否已在黑名单
        if (jwtTokenUtil.checkBlacklist(token)) {
            log.error("token 已被加入黑名单!");
            return false;
        }
 
        // 获取用户信息
        UserTokenInfo userInfoToken = jwtTokenUtil.getUserInfoToken(token);
        // 通过用户信息去判断用户状态,等业务
        
 
        return true;
    }
}

WebConfig 类

import com.example.demo.component.JwtFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
import javax.annotation.Resource;
 
/**
 * @author: zxb
 * @createTime: 2022-10-17 19:02
 * @version: 1.0.0
 * @Description: 请求拦截器
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private JwtFilter jwtFilter;


    /**
     * 不需要拦截地址
     */
    public static final String[] EXCLUDE_URLS = {
            "/login/**"
    };
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtFilter)
                .addPathPatterns("/**")
                .excludePathPatterns(EXCLUDE_URLS);

    }
}

UserTokenInfo类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserTokenInfo {

    private Long userId;
    private String userName;
    private String realName;
}

UserToken类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserToken {

    private Long userId;
    private String userName;
    private String realName;
    private String accessToken;
    private String refreshToken;
}

ResponseResult类

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * @Author
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class ResponseResult<T> {
    /**
     * 状态码
     */
    private Integer code;
    /**
     * 提示信息,如果有错误时,前端可以获取该字段进行提示
     */
    private String msg;
    /**
     * 查询到的结果数据,
     */
    private T data;

    public ResponseResult(T userToken) {
        this.data=userToken;
    }
    public ResponseResult(String msg) {
        this.msg=msg;
    }
}

LoginController

import com.example.demo.model.ResponseResult;
import com.example.demo.model.UserToken;
import com.example.demo.model.UserTokenInfo;
import com.example.demo.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
 
 
/**
 * @author zxb
 * @since 2023-10-18
 */
@RestController
@RequestMapping("/login")
public class LoginController {
 
    @Autowired
    JwtTokenUtil jwtTokenUtil;
 
    /**
     * 登录
     *
     * @return
     */
    @PostMapping("/login")
    public ResponseResult<UserToken> login() {
        // 业务验证:入参校验 + 用户信息校验查询
        // 验证登录成功
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        userTokenInfo.setUserId(1L);
        userTokenInfo.setUserName("zxb");
        userTokenInfo.setRealName("zhang");
 
        // 生成Token
        UserToken userToken = jwtTokenUtil.createToekns(userTokenInfo);
 
        return new ResponseResult<>(userToken);
    }
 
    /**
     * 刷新令牌
     *
     * @param refreshToken
     * @return
     */
    @PostMapping("/refreshToken/{refreshToken}")
    public ResponseResult<UserToken> refreshToken(@PathVariable("refreshToken") String refreshToken) {

        // 判断token是否超时
        if (jwtTokenUtil.isTokenExpired(refreshToken)) {
            return new ResponseResult<>("TOKEN_INVALID");
        }
        // 判断refreshToken是否在黑名单
        if (jwtTokenUtil.checkBlacklist(refreshToken)){
            return new ResponseResult<>("TOKEN_INVALID");
        }
        // 刷新令牌 放入黑名单
        jwtTokenUtil.addBlacklist(refreshToken, jwtTokenUtil.getExpirationDate(refreshToken));
        // 访问令牌 放入黑名单
        String odlAccessToken = jwtTokenUtil.getAccessTokenByRefresh(refreshToken);
        if (!StringUtils.isEmpty(odlAccessToken)) {
            jwtTokenUtil.addBlacklist(odlAccessToken, jwtTokenUtil.getExpirationDate(odlAccessToken));
        }
 
        // 生成新的 访问令牌 和 刷新令牌
        UserTokenInfo userInfoToken = jwtTokenUtil.getUserInfoToken(refreshToken);
 
        // 生成Token
        UserToken userToken = jwtTokenUtil.createToekns(userInfoToken);
 
        return new ResponseResult<>( userToken);
    }
 
 
    /**
     * 登出
     *
     * @return
     */
    @PostMapping("/logOut/{token}")
    public ResponseResult logOut(@PathVariable("token") String token) {
        // 放入黑名单
        jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));
        return new ResponseResult<>("SUCCESS");
    }
 
    /**
     * 注销
     *
     * @return
     */
    @PostMapping("/logOff/{token}")
    public ResponseResult logOff(@PathVariable("token") String token) {
        // 修改用户状态业务

        // 放入黑名单
        jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));
 
        return new ResponseResult<>("SUCCESS");
    }



}

测试

刷新token

 

再次刷新token失败

 因为之前的刷新refreshToken已经使用过一次失效了,不能再次刷新token。

携带accessToken访问其他接口成功

 不携带accessToken访问其他接口失败

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

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

相关文章

多版本CUDA安装切换

系统中默认的安装CUDA为12.0&#xff0c;现在需要在个人用户下安装CUDA11.7。 CUDA 下载 CUDA官网下载 安装 Log file not open.Segmentation fault (core dumped)错误 将/tmp/cuda-installer.log删除即可。重新安装&#xff0c;去掉驱动的安装&#xff0c;设置Toolkit的安装…

ST-LINK 下载器的使用

这两天我也是被这个ST-LINK搞的非常头疼&#xff0c;第一个是固件不兼容&#xff08;也就是keil5的魔法棒中显示ST-LINK connection error&#xff09;&#xff0c;第二个就是STM32 ST-LINK Utility的使用显示dll文件损坏 ST-LINK connection error 先来解决这个问题&#xff…

【附安装包】2023最新版Python安装详细教程!一键安装,永久使用

一、python官网 Python官网主要有python的About (简介)、Downloads (下载)、Documentation(文档)、Community (团体)、Success Stories (成功案例)、News (新闻)、Events (事件动态)等栏目。 Python官网地址&#xff1a;https://www.python.org/ 【领取方式见文末】 二、在…

如何选择感测型离子风机

离子风机在生产车间使用越来越广&#xff0c;对产品的要求也越来越高&#xff0c;而感测型离子风机正好满足。 感测型离子风机:内置感测和反馈功能&#xff1b;2.能快速静电中和及消除&#xff1b;高要求控制离子平衡&#xff1b;3.集感测&#xff0c;联网&#xff0c;通讯数据…

2023年数维杯数学建模C题宫内节育器的生产求解全过程文档及程序

2023年数维杯数学建模 C题 宫内节育器的生产 原题再现&#xff1a; 宫内节育器&#xff08;IUD&#xff09;是一种相对安全、有效、经济、可逆、简便&#xff0c;广大妇女易接受的节育器具&#xff0c;目前已成为我国育龄妇女的主要避孕措施。据悉&#xff0c;我国约70%妇女选…

设定excel导出时单元格的格式

一、需求 要求excel导出时&#xff0c;对应列里面的内容格式为日期&#xff0c;数值格式并有精度要求 &#xff0c;如下图&#xff1a; 使用alibaba&#xff0c;easyexcel&#xff0c;默认的导出数据格式为文本&#xff0c;excel显示为常规&#xff0c;使用数据规范注解Number…

玩转 gpgpu sim 01记 —— try it

1. 短介绍 gpgpu-sim 是一个gpu模拟器&#xff0c;可以让cuda/openCL程序运行在一个软件模拟器上&#xff0c;而不需要硬件GPU&#xff1b; 2. 目标 用最简单省事的方式跑通一个gpgpu-sim的仿真 3. gpgpu-sim 一点项目特性 开发比较早&#xff0c;没有持续的维护&#xff0…

vscode搭建Django自带后台管理系统

文章目录 一、django自带的后台管理系统1. 建表2. 后台管理系统2.1 创建账号2.2 运行后台2.3 登录 二、模版渲染1. 直接将数据渲染到页面2. 数据传递给js 三、数据库1. 查看当前数据库2. 创建UserInfo数据表3. Django rest framework配置 四、vue前端搭建1. 在Django项目的根目…

vue 使用canvas 详细教程

Vue.js 中使用 Canvas Vue.js 是一个流行的 JavaScript 框架&#xff0c;用于构建用户界面。它提供了一种简洁的方式来管理和渲染数据&#xff0c;同时也支持与其他库和工具的集成。要在 Vue.js 中使用 Canvas&#xff0c;您可以按照以下步骤进行操作&#xff1a; 在 Vue.js …

Visual Studio 2022安装SVN插件教程

1. 第一步&#xff1a;避免踩坑&#xff0c;超级重要&#xff01;&#xff01;&#xff01;关闭Visual Studio 2022应用程序&#xff1b;&#xff08;不然插件装不上&#xff0c;一直转圈&#xff01;&#xff09; 2.第二步&#xff1a;下载Visual Studio 2022版本对应的SVN插件…

最新IDE流行度最新排名(每月更新)

2023年09月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&#xff0c;Top IDE索引可以帮助您决定在软件开发项目中使用哪个IDE …

Excel显示列号

默认表格打开列以字母显示 设置方法 文件 -> 工具 -> 选项 -> 常规与保存 设置后效果如下图

2023年在线教育行业研究报告

第一章 行业概况 1.1 定义 随着技术的飞速发展和互联网的普及&#xff0c;我们的学习方式正在经历一场革命。在线教育&#xff0c;作为这场变革的核心&#xff0c;已经成为全球教育领域的热门话题。但究竟什么是在线教育行业呢&#xff1f; 在线教育行业是指通过互联网平台提…

【vue2】data中数据赋值失败找不到、data数据不声明的影响

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;vue2data作用 前言&#xff1a;当你看到这篇文章相比你已经对vue有了一定的了解&#xff0c;对data的有了一个基本的认识&#xff1a;data是存放我们当前页面数据地方。是的&#xff0…

【Python小项目之Tkinter应用】随机点名/抽奖工具大优化:新增选项窗口!可选是否重复点名以及随机点名!可以手动选择文件及文件类型并预览文件!

文章目录 前言一、实现思路窗口逻辑按钮逻辑二、关键代码设置窗口布局实现具体组件实现选择文件与预览文件重中之重:抽取模式三、完整代码总结前言 老规矩,先看效果: 我们为抽奖工具新增了一个设置按钮,点击设置按钮后会出现一个弹窗,弹窗中有各种组件以帮助我们完成初始…

C语言——qsort()函数_学习笔记

本文目录 一、qsort()介绍二、参数详解三、qsort()函数应用举例3.1 排序数组类型的数据3.2 排序结构体类型的数据 四、模拟实现qsort()函数4.1 冒泡排序简单介绍4.2 实现bubble_sort()函数 一、qsort()介绍 qsort()函数是一个库函数&#xff0c;包含在头文件 <stdliib.h>…

Nginx部署前后端分离项目(Linux)

Nginx代理前端页面、后端接口 一、前端打包二、后端打包三、Linux部署Nginx启动、暂停、重启服务器部署文件地址&#xff1a; 一、前端打包 npm run build二、后端打包 通过Maven 使用package打包 三、Linux部署 安装Nginx 安装环境 yum -y install gcc pcre pcre-devel z…

电脑更换硬盘的时候怎么迁移系统?

为什么需要迁移系统&#xff1f; 在一些关于电脑DIY或Windows相关的论坛社区中&#xff0c;有很多人发帖询问怎么迁移系统。那么这个系统迁移&#xff0c;究竟是何含义呢&#xff1f;通俗易懂地解释一下&#xff0c;就是创建一个完整无缺的操作系统复制品&#xff0c;它与系…

硬件总线基础07:PCIe总线基础-事务层(1)

说在开头&#xff1a;关于我的世界&#xff08;4&#xff09; 几年前追过一个综艺&#xff1a;《导演请指教》。不仅仅是因为节目中那一部部小电影的诱惑力&#xff0c;更让人上头的是各方的点评&#xff1a;制片人&#xff0c;学院派&#xff0c;影评人&#xff0c;发行人、大…

Linux系统上安装docker

文章目录 一、Docker的简介二、Docker的组成部分三、Docker的安装命令安装之前先卸载系统上原有的Docker安装需要的安装包yum-utils设置镜像仓库地址安装docker相关的引擎安装docker启动docker查看是否启动使用hello-world镜像测试docker 四、docker run运行思维导图参考文档 一…