条件
- redis(用来存储token以及登录有效期,如果没有不用redis,token可以存在session或者cookie里)
- 每次请求延长过期时间.生成的token有效期设置长一些,存到redis里面,每次请求查看redis是否有token,如果有请求成功redis延长,如果没有就代表在规定时间内没有请求过则登录失效,重新登录,以此循环.
废话不多说,上代码(看注释)
- pom.xml
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.1</version>
</dependency>
- 创建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();
}
}
- 创建jwt.yaml 也可以直接写在方法里或者application.properties/yaml里,写在这方便维护看自己需求
jwt:
# 需要拦截的路径,逗号分割 不能带引号
verify: /**
# 需要放行的路径,逗号分割 不能带引号
skip: /api/**
- 创建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();
}
}
- 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;
}
}
- 请求例子
点赞加关注,代码不迷路~
点赞加关注,代码不迷路~
点赞加关注,代码不迷路~