Springboot 整合 JWT + Redis 实现双Token 校验Demo(简单实现)

news2024/11/18 7:37:57

一、新建一个SpringBoot 项目,springboot项目创建过程详见 

mac idea 创建 springboot 项目_JAVA·D·WangJing的博客-CSDN博客_mac idea创建springboot项目

二、SpringBoot 整合使用 Rdis

SpringBoot 项目 添加 redis配置_JAVA·D·WangJing的博客-CSDN博客_springboot添加redis

三、SpringBoot 整合 JWT

3.1、pom.xml依赖配置

<!-- JWT依赖 -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.7.0</version>

</dependency>

<dependency>

<groupId>com.auth0</groupId>

<artifactId>java-jwt</artifactId>

<version>3.4.0</version>

</dependency>

<!-- JSON 解析器和生成器 -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.83</version>

</dependency>

3.2、application.yml 增加配置

# 应用服务 WEB 访问端口
server:
  port: 8080

spring:
  # 应用名称
  application:
    name: jwt-demo
  # redis配置
  redis:
    # 地址
    host: 127.0.0.1
    # 端口,默认为6379
    port: 6379
    # 连接超时时间
    timeout: 10s
    # 密码
    password: wangjing

# JWT 配置
jwt:
  # 加密密钥
  secret: wangjing
  # header 名称
  header: Authorization
  # token有效时长 S
  expire:
    accessToken: 3600
    refreshToken: 4000

3.3、JwtToken 工具类

package com.wangjing.jwtdemo.util;

import com.alibaba.fastjson.JSONObject;
import com.wangjing.jwtdemo.constants.Constants;
import com.wangjing.jwtdemo.vo.UserToken;
import com.wangjing.jwtdemo.vo.UserTokenInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author: wangjing
 * @createTime: 2022-11-23 14:55
 * @version: 1.0.0
 * @Description: JwtToken 工具类
 */
@Component
public class JwtTokenUtil {

    @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;

    @Autowired
    RedisUtil redisUtil;


    /**
     * 创建 刷新令牌 与 访问令牌 关联关系
     *
     * @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;
    }


    /**
     * 获取 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) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 验证 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();
    }

}

3.4、JwtFilter 拦截器

package com.wangjing.jwtdemo.filter;

import com.wangjing.jwtdemo.util.JwtTokenUtil;
import com.wangjing.jwtdemo.vo.UserTokenInfo;
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: wangjing
 * @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);
        // 通过用户信息去判断用户状态,等业务
        //TODO 涉及到业务,这里不在阐述

        return true;
    }
}

3.5、WebConfig 类(注入拦截器)

package com.wangjing.jwtdemo.config;

import com.wangjing.jwtdemo.filter.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: wangjing
 * @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);
    }
}

3.6、LoginController(简单的代码实现)

package com.wangjing.jwtdemo.controller;


import com.wangjing.jwtdemo.entity.Result;
import com.wangjing.jwtdemo.enums.ResultTypeEnum;
import com.wangjing.jwtdemo.util.JwtTokenUtil;
import com.wangjing.jwtdemo.vo.LoginBody;
import com.wangjing.jwtdemo.vo.UserToken;
import com.wangjing.jwtdemo.vo.UserTokenInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;


/**
 * @author wangjing
 * @since 2022-10-18
 */
@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    JwtTokenUtil jwtTokenUtil;

    /**
     * 登录
     *
     * @param loginBody
     * @return
     */
    @PostMapping("/login")
    public Result<UserToken> login(@RequestBody LoginBody loginBody) {
        // 业务验证:入参校验 + 用户信息校验查询
        //TODO 涉及到业务,这里不在阐述
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        userTokenInfo.setUserId(1L);
        userTokenInfo.setUserName("wangjing");
        userTokenInfo.setRealName("王京");

        // 生成Token
        UserToken userToken = jwtTokenUtil.createToekns(userTokenInfo);

        return new Result<>(ResultTypeEnum.SUCCESS, userToken);
    }

    /**
     * 刷新令牌
     *
     * @param refreshToken
     * @return
     */
    @PostMapping("/refreshToken/{refreshToken}")
    public Result<UserToken> refreshToken(@PathVariable("refreshToken") String refreshToken) {

        // 判断token是否超时
        if (jwtTokenUtil.isTokenExpired(refreshToken)) {
            return new Result<>(ResultTypeEnum.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 Result<>(ResultTypeEnum.TOKEN_INVALID, userToken);
    }


    /**
     * 登出
     *
     * @return
     */
    @PostMapping("/logOut/{token}")
    public Result logOut(@PathVariable("token") String token) {
        // 放入黑名单
        jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));
        return new Result<>(ResultTypeEnum.SUCCESS);
    }

    /**
     * 注销
     *
     * @return
     */
    @PostMapping("/logOff/{token}")
    public Result logOff(@PathVariable("token") String token) {
        // 修改用户状态
        //TODO 涉及到业务,这里不在阐述

        // 放入黑名单
        jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));

        return new Result<>(ResultTypeEnum.SUCCESS);
    }


}

 3.7、本文只列出相对重要的类,具体Demo 下载地址:

https://download.csdn.net/download/wang_jing_jing/87127439

四、通过postman 简单测试

 

注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!

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

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

相关文章

Linux内存映射函数mmap与匿名内存块

学习系列&#xff1a;《APUE14.8》《CSAPP9.8.4》 1 总结 memory-mapped io可以将文件映射到内存中的buffer&#xff0c;当我们从buffer读写数据时&#xff0c;其实操作的是对应文件中的数据。这样可以达到不使用READ/WRITE的IO操作。mmap也可以直接映射匿名内存块&#xff0c…

零信任对企业安全防护能起到什么作用?

随着网络攻击的不断演变&#xff0c;变得更加普遍和复杂&#xff0c;企业正在寻找一种基于身份的网络安全新方法。这些策略和解决方案旨在保护企业内的所有人和机器&#xff0c;并用于检测和防止身份驱动的违规行为。 企业很容易被感染&#xff0c;但问题在于如何找到解决方案。…

基于花朵授粉算法的无线传感器网络部署优化附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

hadoop集群安装(二):克隆服务器集群并免密

文章目录说明分享集群构建集群规划角色划分配置主机名、ip和主机名映射ssh免密总结说明 已 上篇 创建模型虚拟机为基础构建hadoop集群 分享 大数据博客列表开发记录汇总个人java工具库 项目https://gitee.com/wangzonghui/object-tool 包含json、string、集合、excel、zip压缩…

美食杰项目 -- 个人主页(四)

目录前言&#xff1a;具体实现思路&#xff1a;步骤&#xff1a;1. 展示美食杰菜谱大全效果2. 引入element-ui3. 代码总结&#xff1a;前言&#xff1a; 本文给大家讲解&#xff0c;美食杰项目中 实现个人主页的效果&#xff0c;和具体代码。 具体实现思路&#xff1a; 判断是…

如何复用ijkplayer库实现ffmpeg的功能

ijkplayer库介绍 现在ijkplayer播放器应用的非常广泛&#xff0c;很多播放器基本上都是基于ijkplayer二次迭代开发的&#xff0c;众所周知&#xff0c;ijkplayer是基于ffplay的&#xff0c;所以要使用ijkplayer&#xff0c;就必须使用三个so库。 jeffmonyJeffMonydeMacBook-P…

MySQL Server 和 MySQL Workbench安装

对于开发人员来说&#xff0c;只需要安装 MySQL Server 和 MySQL Workbench 这两个软件&#xff0c;就能满足开发的需要了。 ⚫ MySQL Server&#xff1a;专门用来提供数据存储和服务的软件。 ⚫ MySQL Workbench&#xff1a;可视化的 MySQL 管理工具&#xff0c;通过它&#…

会议信息管理系统SSM记录(四)

目录&#xff1a; &#xff08;1&#xff09;部门管理&#xff1a;查询-添加-删除 &#xff08;2&#xff09;部门管理&#xff1a;修改 &#xff08;1&#xff09;部门管理&#xff1a;查询-添加-删除 在DepartmentController中添加方法&#xff1a; 这里getAllDeps方法上篇…

Charles模拟弱网

Charles模拟弱网&#xff0c;适用PC端和移动端&#xff08;IOS&#xff0f;Android&#xff09; 1.打开Proxy->Throttle Settings&#xff0c;以charles 4.2版本为例 2.出现Throttling的界面 3.预设那里有Charles常用的网络设置模拟的数据&#xff0c;根据需要自己选择即可…

【js学习】闭包理解

闭包原理 js使用的是词法作用域&#xff0c;词法作用域的意味着函数执行使用的是定义函数时生效的变量作用域。即函数执行时&#xff0c;里面的参数访问跟执行函数时的作用域无关。如下代码2所示最后输出的是local scope。 闭包定义&#xff1a;js函数对象的内部状态不仅要包…

神经网络的基本工作原理——机器学习

目录 ​编辑 一、实验内容 二、实验过程 1、算法思想 2、算法原理 3、算法分析 三、源程序代码 四、运行结果及分析 五、实验总结 一、实验内容 掌握神经元细胞的数学模型&#xff1b;理解并掌握神经网络的训练过程&#xff1b;理解并掌握神经网络中的矩阵运算&#xff…

Neo4j入门实战

1.介绍 2.实战 Neo4j的sql语句 1.创建多个节点 CREATE (:student {name:小张,age:20}),(:student {name:彭莎丽,age:18})2.匹配节点将节点删除 这样删除是将student中的条件节点删除——>会变成空白节点 match (m:student {name:"Fairy同学"}) remove m:stu…

Docker数据管理

目录 一、数据卷 二、数据卷容器 三、容器互联 管理 Docker容器中数据主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumes&#xff09;数据卷容器&#xff08;DataVolumes Containers&#xff09; 一、数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容…

设计模式之美——基于接口编程

抽象类在被继承时体现的是 is-a 关系&#xff0c;接口在被实现时体现的是 can-do 关系 例如&#xff0c;Plane can fly. Bird can fly&#xff0c;应该把 fly 定义成一个接口。 – 参考 《码出自效Java 开发手册》 函数的命名不能暴露任何实现细节。比如&#xff0c; uploadT…

集合类ArrayList的扩容机制详解

ArrayList类的内部对构造方法进行了重载&#xff0c;提供了无参构造和有参构造两种构造方法。在ArrayList类的内部维护了一个elementData数组用来存放添加的对象。(关于transient关键字&#xff1a;该关键字修饰的属性&#xff0c;不会进行串行化、序列化)。 目录 无参构造扩容…

胡编乱造的自我介绍

写在前面&#xff1a;这篇文章的内容纯属胡编乱造 切勿信以为真。 一、 姓&#xff1a;&#xff08;Black) 布莱克 名&#xff1a;&#xff08;Sirius) 天狼星 中间名&#xff1a;(Orion) 奥莱恩 结束&#xff01; 二、于 2022年3月 入读清华大学信息技术系硕士 &#xff01; 结…

harbor 安装

harbor 安装一.离线安装helm1.下载安装包&#xff0c;上传到服务器2.解压4.验证二.安装harbor1.导入 Harbor源2.下载 Harbor Helm目录3.解压缩4.修改 Harbor Values文件5.部署 Harbor6.访问 Harbor7.验证8.修改daemon.json9.登录Harbor、并push镜像验证10.登录Harbor页面验证是…

基站交直流配电多回路无线智能电量采集监控装置

【摘要】介绍了安科瑞两款多回路无线智能电量采集监控装置&#xff0c;可应用于基站的交直流配电监控、低压出线开关柜集中监控、末端配电箱等集成电力参数监测、电能计量、环境温湿度监测以及无线传输的各类应用场景。 【关键词】无线监控&#xff1b;多回路监控&#xff1b;铁…

OpenFeign源码2-Bean注册过程和调用过程

0. 环境 nacos版本&#xff1a;1.4.1Spring Cloud : Hoxton.SR9&#xff08;没用2020.0.2版本后面说明&#xff09;Spring Boot &#xff1a;2.4.4Spring Cloud alibaba: 2.2.5.RELEASESpring Cloud openFeign 2.2.2.RELEASE 测试代码&#xff1a;github.com/hsfxuebao/s… 20…

深入理解java虚拟机:虚拟机类加载机制(1)

文章目录1. 类加载的时机2. 类加载的过程2.1 加载2.2 验证2.3 准备2.4 解析2.5 初始化1. 类加载的时机 类从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期包括了&#xff1a; 加载&#xff08;Loading&#xff09;验证&#xff08;Verif…