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

条件

  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. 请求例子
    在这里插入图片描述

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

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
实现用户手机号验证码登录可以分为以下几个步骤: 1. 用户输入手机号和验证码,点击登录按钮。 2. 后端接收到手机号和验证码后,先验证验证码是否正确。 3. 如果验证码正确,后端生成JWT token并将token存储到Redis中,同时将token返回给前端。 4. 前端将token存储到本地,以便后续请求时使用。 5. 后续请求时,前端需要在请求头中加入token,后端通过解析token来判断用户是否已登录。 下面是具体实现过程: 1. 在阿里云短信控制台创建短信模板,获取accessKeyId和accessKeySecret。 2. 在Spring Boot项目中添加依赖: ``` <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.0.3</version> </dependency> ``` 3. 实现发送短信验证码的接口: ``` @PostMapping("/sendSms") public Result sendSms(@RequestParam("phone") String phone) { // 生成随机验证码 String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 发送短信验证码 DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "短信签名"); request.putQueryParameter("TemplateCode", "短信模板编号"); request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); try { CommonResponse response = client.getCommonResponse(request); // 将验证码存储到Redis中,有效期为5分钟 redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES); return Result.success("短信验证码发送成功"); } catch (Exception e) { return Result.error("短信验证码发送失败"); } } ``` 4. 实现用户手机号验证码登录的接口: ``` @PostMapping("/login") public Result login(@RequestParam("phone") String phone, @RequestParam("code") String code) { // 验证验证码是否正确 String redisCode = redisTemplate.opsForValue().get(phone); if (StringUtils.isBlank(redisCode)) { return Result.error("验证码已过期,请重新发送"); } if (!redisCode.equals(code)) { return Result.error("验证码不正确"); } // 生成JWT token,并存储到Redis中 String token = JwtUtils.generateToken(phone); redisTemplate.opsForValue().set(phone, token, 1, TimeUnit.DAYS); // 将token返回给前端 return Result.success(token); } ``` 5. 实现JWT token的生成和解析: ``` public class JwtUtils { private static final String SECRET_KEY = "jwt_secret_key"; // JWT密钥 private static final long EXPIRATION_TIME = 7 * 24 * 60 * 60 * 1000; // JWT过期时间(7天) public static String generateToken(String phone) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME); return Jwts.builder() .setSubject(phone) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static String getPhoneFromToken(String token) { try { Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); return claims.getSubject(); } catch (Exception e) { return null; } } } ``` 6. 在拦截器中验证token并获取用户信息: ``` public class JwtInterceptor implements HandlerInterceptor { private static final String AUTH_HEADER = "Authorization"; // token请求头中的名称 @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader(AUTH_HEADER); if (StringUtils.isBlank(token)) { throw new BusinessException("未登录或登录已过期"); } String phone = JwtUtils.getPhoneFromToken(token); if (StringUtils.isBlank(phone)) { throw new BusinessException("无效的token"); } String redisToken = redisTemplate.opsForValue().get(phone); if (StringUtils.isBlank(redisToken) || !redisToken.equals(token)) { throw new BusinessException("未登录或登录已过期"); } return true; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

芳华汉服

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

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

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

打赏作者

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

抵扣说明:

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

余额充值