0


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

一、新建一个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 简单测试

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

标签: spring boot java jwt

本文转载自: https://blog.csdn.net/wang_jing_jing/article/details/128001792
版权归原作者 JAVA·D·WangJing 所有, 如有侵权,请联系我们删除。

“Springboot 整合 JWT + Redis 实现双Token 校验Demo(简单实现)”的评论:

还没有评论