Jwt(Json web token)——从Http协议到session+cookie到Token & Jwt介绍 & Jwt的应用:登陆验证的流程_jwt

28 阅读6分钟

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAsLTD/CwsJ/xoeIf82QUn/Njw5/0ZGRf9LTFH/WVZX/1JVV/9iZmT/ZmNx/1dha/85Qkz/Jyct/w0HBf8OCgf/Cw0P/yEhIv87O0D/RjpI/0tBRv8uLCj/FxsS/x0eGP8bFhn/IyAl/y8xMv9xd3r/eoCJ/0NFVP8gHSX/CAUC/xQXG/8nKC//OjY4/z86Nv8RDRv/AAAZ/wAAGf8UEyL/MyEg/w8AAP8FAAD/DAgH/2hobv+FhZH/MzVA/xUSEv8+QUb/Ly43/ykiK/8BAT3/BARi/wEBaP8BAFj/LCRk/2NCO/80AAH/LAMD/x0BAf8BAAD/PDk+/2pucv82MjX/SUdY/0EvJ/8KCTH/BwWT/wcEpv8CAJr/AAF6/y0ue/+HSEz/XQAA/1EDB/8+Awb/HAEB/wgAAP9nZmr/Yl5l/01OUP8uJiX/AwN5/wMFzP8FCMj/Awa//wAArP8sMJX/lU9R/34AAP96Awb/VwME/y0EAP8XAAD/MCYp/3t6gP9cVk//JSdE/wECyP8ABu7/AQDx/wEA4v8AAMH/LTG4/6NcW/+WAAD/oQAC/5IDAf9UBQD/NwMA/x0EBf92eX7/eWNi/yQkY/8BAu3/AQD9/xwk6P9HUPD/EhPi/y4z1v+tZGD/nAAA/7UDA//RCAb/fgUB/1ICAf8mAAD/goiK/4RuZv8kHnf/AgH9/wAA9v9gce7/9//5/7+++P+Ji+3/qGZq/6MAAP+oAgn/rQcL/4YEBP9aAwH/KAEB/4+Kj/96dm3/IB5X/wYB+P8BAP3/HyXv/8bN9f//29vt/6teZv+dAAH/iwUA/3MCAf9gAwP/PgQD/xcAAP90bnX/Zm5y/yUiNP8GBcT/AgT//wAA/f8vL/L/eHfx/3J37P+tb2v/qwEA/5oBA/9sAwH/UAMD/ywBAf8mEhn/b212/11ugf9aR1D/AQFg/wUG9v8EAvz/BwD6/wAA5f8wNNT/pG1s/54AAP+NBAP/YwQB/zoCAv8NAAD/T01X/05OWP9Nb4T/Xl9f/0M1TP8AAJz/CArl/wIE6f8AALz/ODq0/7Jwbv93AAD/bgQD/0gEAP8WAAD/GxYZ/15aaP8pLDj/X5bB/2iHnP97c3n/KSg4/wMAfv8EAL7/AACc/zczoP+fYWb/YgAA/1EBAf8WAAD/HxMW/2hpdf87R1n/MTM9/zRtnf+Pxev/gpek/2pjZP9gX2z/JCNW/wAATf8bG1X/VT1C/xoAAP8gDg7/PDQ4/1pNVv9QSVr/SEtd/xIRGP9McYj/R3uU/4a+2f9Wbn//SVFQ/2lmXv9oYV//YlVd/2VVVP9eVlf/WFlf/0dKVv8zQUj/Q0xU/yAYIP8CAAT/gABseQAAMyAAAGRlAAAyNAAAdGUAAGluAAAxLAAAdXQAAG50AABBTQAAUFIAAEVTAABSXwAAVkUAADIzgAFSTw=="
style="width: 100px" height="100px">
</body>
</html>

Payload 载荷:比如唯一的用户ID

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

Payload 也使用BASE64编码

Registered claims

这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等

序号名称解释
1iss (issuer)签发人
2exp (expiration time)过期时间
3sub (subject)主题
4aud (audience)受众(接收jwt的一方)
5nbf (Not Before)生效时间
6iat (Issued At)签发时间
7jti (JWT ID)jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

Public claims

这些可以由使用JWT的人员随意定义。

Private claims

这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。

Payload案例

{  "sub": "456781234",  "name": "Mr.zhang",  "admin": true}

Signature:签名

The signature is used to verify the message wasn’t changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

Signature 部分是对前两部分的签名,防止数据篡改。

jwt.io/

Signature部分由编码后的Header、Payload和自定义的秘钥使用Header中指定的算法(HSA256)进行加密签名;

在这里插入图片描述

结构

在这里插入图片描述

jwt使用初步

在这里插入图片描述

导包

<!-- jwt的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.3.0</version>
        </dependency>

生成token

package com.tianju.redisDemo.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JavaJwtTest {
    public static void main(String[] args) {
        // 定义头部header
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");

        // 私钥
        String salt = "pet";
        
        String token = JWT.create()
                .withHeader(header)
                .withClaim("username", "tom")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 60)) // 60s之后过期
                .sign(Algorithm.HMAC256(salt.getBytes()));
        System.out.println(token);

    }
}

获得token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODk3NzY0MTUsInVzZXJuYW1lIjoidG9tIn0.kPmgw03j9g3EJAo2LLzXHd1b7H3uF5zF-0EDEaeonMY

在这里插入图片描述

解析token

DecodedJWT decode = JWT.decode(token);

获取token的内容

System.out.println("Header: " + decode.getHeader());
System.out.println("Payload: " + decode.getPayload());
System.out.println("Audience: " + decode.getAudience());
System.out.println("Signature: " + decode.getSignature());

验证token

JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();

获取信息

System.out.println(build.verify(token).getClaim("data"));

{“name”:”tomcat”,”username”:”tom”}

几种token验证的情况

token的过期时间

com.auth0.jwt.exceptions.TokenExpiredException

package com.tianju.redisDemo.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JavaJwtTest1 {
    public static void main(String[] args) throws InterruptedException {
        // 定义头部header
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");

        // 私钥
        String salt = "pet";

        String token = JWT.create()
                .withHeader(header)
                .withClaim("username", "tom")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 3)) // 60s之后过期
                .sign(Algorithm.HMAC256(salt.getBytes()));
        System.out.println(token);

        Thread.sleep(1000\*5);

        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();
        DecodedJWT verify = jwtVerifier.verify(token);
        System.out.println(verify.getClaim("username"));
    }
}

在这里插入图片描述

token的内容被改过

com.auth0.jwt.exceptions.JWTDecodeException

package com.tianju.redisDemo.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JavaJwtTest1 {
    public static void main(String[] args) throws InterruptedException {
        // 定义头部header
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");

        // 私钥
        String salt = "pet";

        String token = JWT.create()
                .withHeader(header)
                .withClaim("username", "tom")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 3)) // 60s之后过期
                .sign(Algorithm.HMAC256(salt.getBytes()));
        System.out.println(token);

        token = token.replaceAll("[a-zA-Z]","X");


        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();
        DecodedJWT verify = jwtVerifier.verify(token);
        System.out.println(verify.getClaim("username"));
    }
}

在这里插入图片描述

token的载荷被更改

在这里插入图片描述

在这里插入图片描述

package com.tianju.redisDemo.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JavaJwtTest1 {
    public static void main(String[] args) throws InterruptedException {
        // 定义头部header
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");

        // 私钥
        String salt = "pet";

        String token = JWT.create()
                .withHeader(header)
                .withClaim("username", "tom")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 60 \* 30)) //
                .sign(Algorithm.HMAC256(salt.getBytes()));
        System.out.println("登陆成功产生token:"+token);

// token = token.replaceAll("[a-zA-Z]","X");

        String hackToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODk3Nzk1MjMsInVzZXJuYW1lIjoicGV0In0.laRjGZsM5nq8IYPBMSYCbMdxyFMiXaZXKg9F7WL-n-Q";

        System.out.println("被改过的token:"+hackToken);
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();
        DecodedJWT verify = jwtVerifier.verify(hackToken);
        System.out.println(verify.getClaim("username"));
        System.out.println("签名:"+ verify.getSignature());
    }
}

Jwt的应用:登陆验证的流程

在这里插入图片描述

整体的流程:

第一步:产生30分钟有效时间的token;

第二步:在redis里面存储token,redis里面有效时间为60分钟;

第三步:token过期,但是Redis里面的token还没有过期,此时进行续期;

第四步:给原有的token续期,需要设置最大续期时间,目前用最大续期次数解决;

用户表

用户名、密码、电话号码、邮箱

在这里插入图片描述

user.java实体类

package com.tianju.redisDemo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user\_owner")
public class User {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    @TableField("username")
    private String username;

    @TableField("realname")
    private String realName;

    @TableField("password")
    private String password;

    @TableField("tel")
    private String tel;

    @TableField("security\_key")
    private String securityKey;

    @TableField("create\_time")
    private Date createTime;
}

dao数据库

UserMapper.java文件

package com.tianju.redisDemo.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tianju.redisDemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

service业务

处理登陆业务:抛出异常:

(1)throw new UsernameIsEmptyException(“输入为空异常”);

(2)throw new UsernameNotFoundException(“用户名不存在异常”); 后面用布隆过滤器

(3)throw new UsernameOrPasswordErrorException(“用户名或者密码错误异常”);

package com.tianju.redisDemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tianju.redisDemo.dao.UserMapper;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.exception.UsernameIsEmptyException;
import com.tianju.redisDemo.exception.UsernameNotFoundException;
import com.tianju.redisDemo.exception.UsernameOrPasswordErrorException;
import com.tianju.redisDemo.service.IUserService;
import com.tianju.redisDemo.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<UserVo> queryAll() {
        List<UserVo> userVolist = new LinkedList<>();
        List<User> userList = userMapper.selectList(null);
        for (User user : userList) {
            userVolist.add(
                    new UserVo(user.getUsername(), user.getRealName(), user.getCreateTime())
            );
        }
        return userVolist;

    }


    /\*\*
 \* 登陆的业务
 \* @param username
 \* @param password
 \*/
    @Override
    public User login(String username, String password) {
        if (username==null || password==null||
           "".equals(username)||"".equals(password)){
            // 抛出异常,USERNAME\_IS\_EMPTY\_EXCEPTION
            throw new UsernameIsEmptyException("输入为空异常");
        }
        // 判断用户名是否存在
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername,username);
        User user = userMapper.selectOne(lambdaQueryWrapper);

        if (Objects.isNull(user)){
            // 用户名不存在
            // 抛出异常:USERNAME\_NOT\_FOUND\_EXCEPTION
            throw new UsernameNotFoundException("用户名不存在异常");
        }

        if (!user.getPassword().equals(password)){
            // 密码不对
            // 抛出异常:USERNAME\_OR\_PASSWORD\_ERROR\_EXCEPTION
            throw  new UsernameOrPasswordErrorException("用户名或者密码错误异常");
        }

        log.debug("用户名{}登陆系统成功",username);
        return user;
    }
}

定义异常

在这里插入图片描述

在这里插入图片描述

package com.tianju.redisDemo.exception;

/\*\*
 \* 非法用户
 \*/
public class IllegalUserException extends RuntimeException{
    public IllegalUserException(String message) {
        super(message);
    }
}

package com.tianju.redisDemo.exception;

/\*\*
 \* 用户名。或者密码未输入异常
 \*/
public class UsernameIsEmptyException extends RuntimeException{
    public UsernameIsEmptyException(String message) {
        super(message);
    }
}

package com.tianju.redisDemo.exception;

/\*\*
 \* 用户未登录
 \*/
public class UserNotLoginException extends RuntimeException{
    public UserNotLoginException(String message) {
        super(message);
    }
}

异常的拦截处理

controller层调用service时,会抛出异常,捕获controller层的异常,进行处理,返回响应

package com.tianju.redisDemo.exception.handle;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import com.tianju.redisDemo.exception.\*;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Date;

/\*\*
 \* 拦截controller层的异常,捕获异常,进行处理;
 \* 用户登陆异常
 \*/
@RestControllerAdvice
public class UserExceptionHandler {

    @ExceptionHandler(UsernameIsEmptyException.class)
    public HttpResp usernameIsEmptyHandler(UsernameIsEmptyException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }
    @ExceptionHandler(UsernameNotFoundException.class)
    public HttpResp usernameNotFoundHandler(UsernameNotFoundException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }
    @ExceptionHandler(UsernameOrPasswordErrorException.class)
    public HttpResp usernameOrPasswordErrorHandler(UsernameOrPasswordErrorException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }

    @ExceptionHandler(UserNotLoginException.class)
    public HttpResp userNotLoginHandler(UserNotLoginException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }
    @ExceptionHandler(IllegalUserException.class)
    public HttpResp illegalUserHandler(IllegalUserException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }

// @ExceptionHandler(TokenExpiredException.class)
// public HttpResp tokenExpiredHandler(TokenExpiredException e){
// return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
// }

    @ExceptionHandler(UserNeedRenewTokenException.class)
    public HttpResp userNeedRenewTokenHandler(UserNeedRenewTokenException e){
        return new HttpResp(ResultCode.USER\_LOGIN\_ERROR,new Date(),e.getMessage());
    }
}

在这里插入图片描述

controller层

UserController.java文件

package com.tianju.redisDemo.controller;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Api(tags = "用户Api")
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IUserService userService;

    @Value("${lifespanToken}")
    private Integer lifespanToken;

    @Value("${lifespanRedis}")
    private Integer lifespanRedis;

    @ApiOperation(value = "login",notes = "用户登陆api")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username",required = true),
            @ApiImplicitParam(name = "password",required = true)
    })
    @PostMapping("/login")
    public HttpResp login(String username, String password, HttpServletResponse response,HttpServletRequest request){
        User user = userService.login(username, password);// 这里有可能出现异常
        // 能走到这一步,说明登陆成功,产生token

        // 第一步:产生30分钟有效时间的token
        String securityKey = user.getSecurityKey();
        String token = createToken(username, securityKey);

        // 存入redis里面,用户名和秘钥,以token为键,所以只要token被改动,就会认为非法用户
        stringRedisTemplate.opsForHash().put(token, "username", username);
        stringRedisTemplate.opsForHash().put(token, "securityKey", securityKey);

        // redis过期晚一点,双token机制
        // 第二步:在redis里面存储token,redis里面有效时间为60分钟;
        stringRedisTemplate.expireAt(token, new Date(System.currentTimeMillis()+lifespanRedis));

        // 3.token要给前端
        response.addHeader("bm\_token",token);

        // 在session里面存续期的次数
        request.getSession().setAttribute("renewTokenTimes", 0);
        return HttpResp.results(ResultCode.USER\_LOGIN\_SUCCESS,new Date(),token);

    }

    /\*\*
 \* 产生token对象
 \* @param username 用户名
 \* @param securityKey 安全码
 \* @return
 \*/
    private String createToken(String username,String securityKey){
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");
        // 链式写法
        return JWT.create()
                .withHeader(header)
                .withClaim("username", username)
// .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 3)) // 60\*30
                // 第一步:产生30分钟有效时间的token
                .withExpiresAt(new Date(System.currentTimeMillis() + lifespanToken)) // 60\*30
                .sign(Algorithm.HMAC256(securityKey.getBytes()));
    }

    @ApiOperation(value = "findAll",notes = "用户登陆api")
    @GetMapping("/findAll")
    public HttpResp findAll(){
        // 使用拦截器处理没有token的情况
        return HttpResp.results(ResultCode.USER\_FIND\_SUCCESS,new Date(),userService.queryAll());
    }
}

swagger测试

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用拦截器统一拦截方式

AuthInterceptor.java文件
TokenExpiredException异常

在这里插入图片描述

package com.tianju.redisDemo.interceptor;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tianju.redisDemo.exception.IllegalUserException;
import com.tianju.redisDemo.exception.UserNeedRenewTokenException;
import com.tianju.redisDemo.exception.UserNotLoginException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${lifespanToken}")
    private Integer lifespanToken;

    @Value("${lifespanRedis}")
    private Integer lifespanRedis;

    @Value("${MAX\_RENEW\_TOKEN\_TIME}")
    private Integer MAX\_RENEW\_TOKEN\_TIME;// 最大续期次数

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("bm\_token");
        if (token==null || "".equals(token)){
            throw new UserNotLoginException("用户没有登陆");
        }
        // 如何获取token
        String securityKey = (String) stringRedisTemplate.opsForHash().get(token, "securityKey");
        if (securityKey==null){
            throw new IllegalUserException("非法用户");
        }

        // 过期异常,篡改异常

        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(securityKey)).build();
        try {
            // 如果token过期,会抛出com.auth0.jwt.exceptions.TokenExpiredException
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
        } catch (TokenExpiredException e) { // token过期,redis没有过期
            // 第三步:token过期,但是Redis里面的token还没有过期,此时进行续期
            String username = (String) stringRedisTemplate.opsForHash().get(token, "username");

            // 方案一:产生新的token给前端;
// String newToken = removeTokenMakeNewToken(token, username, securityKey);
// response.addHeader("bm\_token",token);

            // 方案二:给原有的token续期,需要设置最大续期时间
            Integer renewTokenTimes = (Integer) request.getSession().getAttribute("renewTokenTimes");
            request.getSession().setAttribute("renewTokenTimes",renewTokenTimes+1);

            // 产生一个新的token,更新redis
            log.info( ">>>>>>>>>"+username+"的token过期,即将进行续期,续期次数:"+renewTokenTimes);

            if (renewTokenTimes<MAX\_RENEW\_TOKEN\_TIME){
                System.out.println("我输续期");
                stringRedisTemplate.expireAt(token, new Date(System.currentTimeMillis()+lifespanRedis));
            }
            else {
                System.out.println("不再续期");
                throw new UserNeedRenewTokenException("达到最大续期次数,需要重新登陆");
            }
        }
        return true;
    }

    private String removeTokenMakeNewToken(String token,String username,String securityKey){
        // 1.产生新的token
        Map<String,Object> header = new HashMap<>();
        header.put("alg", "HS256");
        // 链式写法
        String newToken = JWT.create()
                .withHeader(header)
                .withClaim("username", username)
// .withExpiresAt(new Date(System.currentTimeMillis() + 1000 \* 3)) // 60\*30
                .withExpiresAt(new Date(System.currentTimeMillis() + lifespanToken)) // 60\*30
                .sign(Algorithm.HMAC256(securityKey.getBytes()));

        // 2.更新redis
        stringRedisTemplate.opsForHash().delete(token,"\*"); // 删除之前的token
        stringRedisTemplate.opsForHash().put(newToken, "username", username);
        stringRedisTemplate.opsForHash().put(newToken, "securityKey", securityKey);
        // redis过期晚一点,双token机制
        stringRedisTemplate.expireAt(newToken, new Date(System.currentTimeMillis()+lifespanRedis));

        // 3.新的token还要给前端
        return newToken;
// response.addHeader("bm\_token",token);
    }
}


在这里插入图片描述

配置拦截器 @Configuration

package com.tianju.redisDemo.config;

import com.tianju.redisDemo.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;