SpringBoot+JWT+redis实现拦截器(每次请求更新token过期时间)

该博客介绍了使用Java和Spring Boot结合Redis实现Token登录验证的方法。Redis用于存储Token及登录有效期,每次请求延长过期时间,若规定时间内无请求则登录失效。还给出了创建相关Java类、配置文件的步骤,以及Controller登录处理和请求例子。

条件

  1. redis(用来存储token以及登录有效期,如果没有不用redis,token可以存在session或者cookie里)
  2. 每次请求延长过期时间.生成的token有效期设置长一些,存到redis里面,每次请求查看redis是否有token,如果有请求成功redis延长,如果没有就代表在规定时间内没有请求过则登录失效,重新登录,以此循环.

废话不多说,上代码(看注释)

  1. pom.xml
		<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.1</version>
        </dependency>
  1. 创建JwtUtil.java 用token获取用户id,以及使用用户id生成token(可以加上其他信息一起)
package com.ly.remind.common.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ly.remind.common.constants.CommonConstants;
import lombok.Data;

import java.util.Calendar;

/**
 * @ClassName: JWTUtil.java
 * @Program: com.ly.remind.common.util.JwtUtil
 * @Date: 2022/10/26 上午10:27
 * @Author: Zhaop
 * @Description: jwt token util
 */
@Data
public class JwtUtil {
    // 任意字符串
    private static final String SING = "!A;E]R'T'!S-*G-S*'S[;HS.HH]D*S-VS+D=GS-=";

    private static String token;
    
    // 过期时间 小时 可以设的长一点,通过redis的过期时间来决定用户登录的有效时长
    private static final Integer EXPIRATION_TIME = 24*30;


    /***
     *@Author: Zhaop
     *@Params: [id, ttlMinute:过期时间,分钟]
     *@Return: java.lang.String
     *@Date: 2022/10/26 下午12:31
     *@Description: 生成用户token
     *@Notes:
     */
    public static String getJWToken(String id) {

        Calendar instance = Calendar.getInstance();
        // 设置过期时间
        instance.add(Calendar.HOUR, EXPIRATION_TIME);

        JWTCreator.Builder builder = JWT.create();
        // 指定标识字段
        builder.withClaim(CommonConstants.JwtConstants.TOKEN_KEY, id);
        // 指定过期时间
        token = builder.withExpiresAt(instance.getTime())
                // 指定生成算法及签名
                .sign(Algorithm.HMAC256(SING));

        return token;
    }

    /***
     *@Author: Zhaop
     *@Params: [token]
     *@Return: boolean
     *@Date: 2022/10/26 下午12:36
     *@Description: 验证token, 返回true或false
     *@Notes:
     */
    public static boolean verify(String token) {
        try {
            JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /****
     *@Author: Zhaop
     *@Params: [token]
     *@Return: com.auth0.jwt.interfaces.DecodedJWT
     *@Date: 2022/10/26 下午12:35
     *@Description: 验证token, 正确通过, 否则抛出异常
     *@Notes:
     */
    public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }

    /***
     *@Author: Zhaop
     *@Params: [token]
     *@Return: int
     *@Date: 2022/10/26 下午12:35
     *@Description: 从token中获取用户id
     *@Notes:
     */
    public static String getIdByToken(String token) {
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
        return verify.getClaim(CommonConstants.JwtConstants.TOKEN_KEY).asString();
    }


}



  1. 创建jwt.yaml 也可以直接写在方法里或者application.properties/yaml里,写在这方便维护看自己需求
jwt:
  # 需要拦截的路径,逗号分割 不能带引号
  verify: /**
  # 需要放行的路径,逗号分割 不能带引号
  skip: /api/**
  1. 创建WebMvcConfig.java 实现WebMvcConfigurer接口做拦截逻辑(重要)
package com.ly.remind.config;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.ly.remind.common.constants.CommonConstants;
import com.ly.remind.common.constants.RedisConstants;
import com.ly.remind.common.enums.ResponseEnum;
import com.ly.remind.common.util.JwtUtil;
import com.ly.remind.common.util.RedisUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName: WebMvcConfig.java
 * @Program: com.ly.remind.config.WebMvcConfig
 * @Date: 2022/10/26 上午10:33
 * @Author: Zhaop
 * @Description: 拦截器以及拦截处理
 * 
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt") 
@PropertySource(value = {"classpath:jwt.yaml"})
public class WebMvcConfig implements WebMvcConfigurer {
    @Value("${verify}")
    private String[] verify;
    @Value("${skip}")
    private String[] skip;

    @Autowired
    private RedisUtil redisUtil;
    /***
     *@Author: Zhaop
     *@Params: [request, response, handler]
     *@Return: boolean
     *@Date: 2022/10/26 下午6:35
     *@Description: 拦截处理
     *@Notes: 
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 用户拦截器
        registry.addInterceptor(
                new HandlerInterceptor() {
                    @Override
                    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                        // 如果不是映射到方法直接通过
                        if (!(handler instanceof HandlerMethod)) {
                            return true;
                        }
						
						// 从请求中获取token
                        String token = request.getHeader("token");

                        // 捕获刚刚JWT中抛出的异常,并封装对应的返回信息
                        try {
                             // 通过token获取id
                            String userId = JwtUtil.getIdByToken(token);
                            String s = redisUtil.get(RedisConstants.USER_TOKEN_KEY + userId);
                            // token不存在,或者token不同均不放行
                            if (StringUtils.isBlank(s) || !token.equals(s)) {
                                throw new TokenExpiredException("未登录!");
                            }
                            //请求成功 redis 延长登录的过期时间
                            redisUtil.setExOfMill(RedisConstants.USER_TOKEN_KEY + userId, token, CommonConstants.JwtConstants.EXPIRATION_TIME_30M);
                            // 验证通过刷新redis-token
                            return true;
                        } catch (SignatureVerificationException e) {
                            // 无效签名
                            ResponseEnum.USER_NOT_LOGGED_IN.assertNotNull(null);
                        } catch (TokenExpiredException e) {
                            // 已过期
                            ResponseEnum.USER_NOT_LOGGED_IN.assertNotNull(null);
                        } catch (AlgorithmMismatchException e) {
                            // 算法不一致
                            ResponseEnum.USER_NOT_LOGGED_IN.assertNotNull(null);
                        } catch (Exception e) {
                            // 无效身份信息
                            ResponseEnum.USER_NOT_LOGGED_IN.assertNotNull(null);
                        }
                        return false;
                    }
                }
        )
                // 需要拦截的请求
                .addPathPatterns(verify)
                // 需要放行的请求
                .excludePathPatterns(skip);
    }

    /**
     * 跨域支持
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }

    @Bean
    public WebMvcConfig getWebMvcConfig() {
        return new WebMvcConfig();
    }

}

  1. Controller登录中的处理 主要是登录之后的处理
package com.ly.remind.controller;


import com.ly.remind.common.constants.CommonConstants;
import com.ly.remind.common.constants.RedisConstants;
import com.ly.remind.common.resp.ResultMessage;
import com.ly.remind.common.util.JwtUtil;
import com.ly.remind.common.util.RedisUtil;
import com.ly.remind.service.IUserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * <p>
 * 用户信息表 前端控制器
 * </p>
 *
 * @author Zhaop
 * @since 2022-10-25
 */
@RestController
public class UserController {
    @Resource
    private IUserService iUserService;
    @Resource
    private RedisUtil redisUtil;

    @PostMapping("/api/loggin")
    public ResultMessage login() {
    	// user-id123就是用户id,我这里直接写死了,程序中改成动态的
        String token = JwtUtil.getJWToken("user-id123");
        // 存储到redis中,在禁用用户的同时可以强制踢下线 过期时间30分钟
        redisUtil.setExOfMill(RedisConstants.USER_TOKEN_KEY + "user-id123", token, CommonConstants.JwtConstants.EXPIRATION_TIME_30M);
        ResultMessage resultMessage = new ResultMessage();
        resultMessage.setCode(1);
        resultMessage.setMsg("ok");
        // token返回给前端,每次请求需带上(放在请求头)
        resultMessage.setData(token);
        return resultMessage;
    }

    @GetMapping("/user/userinfo")
    public ResultMessage userinfo() {
        ResultMessage resultMessage = new ResultMessage();
        resultMessage.setCode(1);
        resultMessage.setMsg("ok");
        return resultMessage;
    }
}
  1. 请求例子
    在这里插入图片描述

点赞加关注,代码不迷路~
点赞加关注,代码不迷路~
点赞加关注,代码不迷路~

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhopq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值