目录
1.JWT简介
JWT:全称是JSON Web Token,是token的一种实现方法。通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT结构:JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.
进行连接形成最终传输的字符串:
header中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。
payload部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择:
当然,用户也可以根据自己业务使用需要自行更改添加字段。
Signature部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,使用指定秘钥,通过指定的签名算法生成哈希,以确保数据不会被篡改。该密码仅仅为保存在服务器中,并且不能向用户公开。
JWT优势:
- 跨语言:因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
- 适合单点登录:不需要在服务端保存会话信息,也就是说不依赖于cookie和session,适用于分布式微服务
- 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端
2.登录鉴权流程
项目以springcloud框架实现,其总体框架如下:
cloud-auth-server 为登录认证微服务,实现用户登录登出功能(以shiro实现,此处不进行展开,具体可参考:springboot集成shiro实现用户登录认证)
cloud-gateway为统一网关,实现路由管理和token鉴权
cloud-jwt-manage为JWT生成存放微服务,存放生成jwt token的相关工具类。
其他微服务实现用户登录鉴权未使用到,不进行展开。
springcloud gateway实现登录鉴权流程如下;
简单来说就是用户需要先进行登录操作,验证用户名密码,获得jwt令牌。再携带jwt令牌发送请求,通过网关转发,访问具体的业务接口;若没有携带jwt令牌,或者令牌已过期,则无法访问对应的业务接口,需要重新登录。
3.springcloud gateway简单使用
首选,需要导入springcloud gateway相关依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
需要注意的是,gateway相关依赖和 springboot web相关依赖会冲突,如果在其他包中使用了,需要将其排除:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</exclusion>
</exclusions>
</dependency>
编写配置文件:
application.yml:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
# redis配置
redis:
database: 0
host: 127.0.0.1
#redis默认端口
port: 6379
password:
timeout: 5000ms
profiles:
include: routers #使得 application-routers.yml配置文件生效
auth:
jwt:
enabled: true # 是否开启JWT登录认证功能
secret: passjava # JWT 私钥,用于校验JWT令牌的合法性
expiration: 3600000 # JWT 令牌的有效期,用于校验JWT令牌的合法性,一个小时
header: Authorization # HTTP 请求的 Header 名称,该 Header作为参数传递 JWT 令牌
userParamName: userId # 用户登录认证用户名参数名称
pwdParamName: password # 用户登录认证密码参数名称
useDefaultController: true # 是否使用默认的JwtAuthController
skipValidUrl: /auth/login
需注意,auth.jwt中相关配置信息cloud-auth-server配置文件中也要有一样的配置,避免cloud-auth-server微服务生成token时属性空值。
application-routers.yml:
spring:
cloud:
gateway:
routes:
- id: route_auth # 认证微服务路由规则
uri: lb://cloud-auth-server # 负载均衡,将请求转发到注册中心注册的 auth 服务进行认证
predicates: # 断言
- Path=/api/auth/** # 如果前端请求路径包含 api/auth,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空
然后,我们就可以通过http://localhost:9527/api/auth/login 地址进行登录
4.创建Token
编写cloud-jwt-manage相关代码:
导入依赖:
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>compile</scope>
</dependency>
<!-- redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- jwt 工具类 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
<scope>compile</scope>
</dependency>
<!-- 自定义配置项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
jwt相关属性类:
@Data
@ConfigurationProperties(prefix = "auth.jwt")
@Component
public class AuthJwtProperties {
//是否开启JWT,即注入相关的类对象
private Boolean enabled = true;
//JWT 密钥
private String secret;
//accessToken 有效时间
private Long expiration;
//header名称
private String header;
/**
* 用户登录-用户名参数名称
*/
private String userParamName = "userId";
/**
* 用户登录-密码参数名称
*/
private String pwdParamName = "password";
//是否使用默认的JWTAuthController
private Boolean useDefaultController = false;
//跳过认证的路由
private String skipValidUrl;
}
public class TokenConstants
{
/**
* 令牌自定义标识
*/
public static final String AUTHENTICATION = "Authorization";
/**
* 令牌前缀
*/
public static final String PREFIX = "Bearer ";
}
jwt 令牌生成、验证工具类:
import com.seven.springcloud.config.AuthJwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class JwtTokenUtil {
private static final String JWT_CACHE_KEY = "jwt:userId:";
private static final String USER_ID = "userId";
private static final String USER_NAME = "username";
private static final String ACCESS_TOKEN = "access_token";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String EXPIRE_IN = "expire_in";
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private AuthJwtProperties jwtProperties;
/**
* 生成 token 令牌主方法
* @param userId 用户Id或用户名
* @return 令token牌
*/
public Map<String, Object> generateTokenAndRefreshToken(String userId, String username) {
//生成令牌及刷新令牌
Map<String, Object> tokenMap = buildToken(userId, username);
//redis缓存结果
cacheToken(userId, tokenMap);
return tokenMap;
}
//将token缓存进redis
private void cacheToken(String userId, Map<String, Object> tokenMap) {
stringRedisTemplate.opsForHash().put(JWT_CACHE_KEY + userId, ACCESS_TOKEN, tokenMap.get(ACCESS_TOKEN));
stringRedisTemplate.opsForHash().put(JWT_CACHE_KEY + userId, REFRESH_TOKEN, tokenMap.get(REFRESH_TOKEN));
stringRedisTemplate.expire(userId, jwtProperties.getExpiration() * 2, TimeUnit.MILLISECONDS);
}
//生成令牌
private Map<String, Object> buildToken(String userId, String username) {
//生成token令牌
String accessToken = generateToken(userId, username, null);
//生成刷新令牌
String refreshToken = generateRefreshToken(userId, username, null);
//存储两个令牌及过期时间,返回结果
HashMap<String, Object> tokenMap = new HashMap<>(2);
tokenMap.put(ACCESS_TOKEN, accessToken);
tokenMap.put(REFRESH_TOKEN, refreshToken);
tokenMap.put(EXPIRE_IN, jwtProperties.getExpiration());
return tokenMap;
}
/**
* 生成 token 令牌 及 refresh token 令牌
* @param payloads 令牌中携带的附加信息
* @return 令牌
*/
public String generateToken(String userId, String username,
Map<String,String> payloads) {
Map<String, Object> claims = buildClaims(userId, username, payloads);;
return generateToken(claims);
}
public String generateRefreshToken(String userId, String username, Map<String,String> payloads) {
Map<String, Object> claims = buildClaims(userId, username, payloads);
return generateRefreshToken(claims);
}
//构建map存储令牌需携带的信息
private Map<String, Object> buildClaims(String userId, String username, Map<String, String> payloads) {
int payloadSizes = payloads == null? 0 : payloads.size();
Map<String, Object> claims = new HashMap<>(payloadSizes + 2);
claims.put("sub", userId);
claims.put("username", username);
claims.put("created", new Date());
//claims.put("roles", "admin");
if(payloadSizes > 0){
claims.putAll(payloads);
}
return claims;
}
/**
* 刷新令牌并生成新令牌
* 并将新结果缓存进redis
*/
public Map<String, Object> refreshTokenAndGenerateToken(String userId, String username) {
Map<String, Object> tokenMap = buildToken(userId, username);
stringRedisTemplate.delete(JWT_CACHE_KEY + userId);
cacheToken(userId, tokenMap);
return tokenMap;
}
/**
* 从request获取userid
* @param request http请求
* @return request.getHeader
*/
public String getUserIdFromRequest(HttpServletRequest request) {
return request.getHeader(USER_ID);
}
//缓存中删除token
public boolean removeToken(String userId) {
return Boolean.TRUE.equals(stringRedisTemplate.delete(JWT_CACHE_KEY + userId));
}
/**
* 从令牌中获取用户id
*
* @param token 令牌
* @return 用户id
*/
public String getUserIdFromToken(String token) {
String userId;
try {
Claims claims = getClaimsFromToken(token);
userId = claims.getSubject();
} catch (Exception e) {
userId = null;
}
return userId;
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = (String) claims.get(USER_NAME);
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 判断令牌是否不存在 redis 中
*
* @param token 刷新令牌
* @return true=不存在,false=存在
*/
public Boolean isRefreshTokenNotExistCache(String token) {
String userId = getUserIdFromToken(token);
String refreshToken = (String)stringRedisTemplate.opsForHash().get(JWT_CACHE_KEY + userId, REFRESH_TOKEN);
return refreshToken == null || !refreshToken.equals(token);
}
/**
* 判断令牌是否过期
*
* @param token 令牌
* @return true=已过期,false=未过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
//验证 JWT 签名失败等同于令牌过期
return true;
}
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 验证令牌
*
* @param token 令牌
* @param userId 用户Id用户名
* @return 是否有效
*/
public Boolean validateToken(String token, String userId) {
String username = getUserIdFromToken(token);
return (username.equals(userId) && !isTokenExpired(token));
}
/**
* 生成令牌
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis()
+ jwtProperties.getExpiration());
return Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512,
jwtProperties.getSecret())
.compact();
}
/**
* 生成刷新令牌 refreshToken,有效期是令牌的 2 倍
* @param claims 数据声明
* @return 令牌
*/
private String generateRefreshToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 2);
return Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
.compact();
}
/**
* 从令牌中获取数据声明,验证 JWT 签名
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
}
上述代码中:
此方法中存放业务所需存放的信息,比如用户名,id等,生成的jwt解码后,payload中可查得以上信息。
方法将生产的token缓存进redis中,进行保存。
然后,我们需要将cloud-jwt-manage进行clean、install,以便其他微服务进行使用,其导入方式如下:
<!--自定义包-->
<dependency>
<groupId>springcloud2022</groupId>
<artifactId>cloud-jwt-manage</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
因网关中web依赖和gateway依赖会冲突,以及网关中未导入数据库连接相关配置,所以需要排除以上依赖再讲自定义jwt包导入。cloud-auth-server微服务中直接导入即可。
5.实现登录鉴权
下面进行登录授权,首先在cloud-auth-manage登录控制类方法中,进行登录验证,通过后,调用以下代码生成token返回结果:
// 通过 jwtTokenUtil 生成 JWT 令牌和刷新令牌
Map<String, Object> tokenMap = jwtTokenUtil
.generateTokenAndRefreshToken(String.valueOf(account.getId()), username);
然后在cloud-gateway微服务中,编写过滤器:
//过滤器,使得JWTToken过滤器生效
@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
import com.alibaba.fastjson.JSON;
import com.seven.springcloud.config.AuthJwtProperties;
import com.seven.springcloud.constants.TokenConstants;
import com.seven.springcloud.entities.CommonResult;
import com.seven.springcloud.entities.enums.ResponseCodeEnum;
import com.seven.springcloud.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Slf4j
@Configuration
public class JwtAuthCheckFilter {
private static final String AUTH_TOKEN_URL = "/auth/login";
private static final String REFRESH_TOKEN_URL = "/auth/token/refresh";
public static final String USER_ID = "userId";
public static final String USER_NAME = "username";
public static final String FROM_SOURCE = "from-source";
@Resource
private AuthJwtProperties authJwtProperties;
@Resource
private JwtTokenUtil jwtTokenUtil;
@Bean
@Order(-101)
public GlobalFilter jwtAuthGlobalFilter() {
return (exchange, chain) -> {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpResponse serverHttpResponse = exchange.getResponse();
ServerHttpRequest.Builder mutate = serverHttpRequest.mutate();
String requestUrl = serverHttpRequest.getURI().getPath();
// 跳过对登录请求的 token 检查。因为登录请求是没有 token 的,是来申请 token 的。
if(AUTH_TOKEN_URL.equals(requestUrl)) {
log.info("登录url,放行");
return chain.filter(exchange);
}
// 从 HTTP 请求头中获取 JWT 令牌
String token = getToken(serverHttpRequest);
if (StringUtils.isEmpty(token)) {
return unauthorizedResponse(exchange, serverHttpResponse, ResponseCodeEnum.TOKEN_MISSION);
}
// 对Token解签名,并验证Token是否过期
boolean isJwtNotValid = jwtTokenUtil.isTokenExpired(token);
if(isJwtNotValid){
return unauthorizedResponse(exchange, serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
}
// 验证 token 里面的 userId 是否为空
String userId = jwtTokenUtil.getUserIdFromToken(token);
String username = jwtTokenUtil.getUserNameFromToken(token);
if (StringUtils.isEmpty(userId)) {
return unauthorizedResponse(exchange, serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
}
// 设置用户信息到请求
addHeader(mutate, USER_ID, userId);
addHeader(mutate, USER_NAME, username);
// 内部请求来源参数清除
removeHeader(mutate, FROM_SOURCE);
return chain.filter(exchange.mutate().request(mutate.build()).build());
};
}
//添加头部信息
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
if (value == null) {
return;
}
String valueStr = value.toString();
String valueEncode = urlEncode(valueStr);
mutate.header(name, valueEncode);
}
//移除头部信息
private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
}
//内容编码,配置为UTF-8
static String urlEncode(String str) {
try {
return URLEncoder.encode(str, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
return StringUtils.EMPTY;
}
}
//请求token
private String getToken(ServerHttpRequest request) {
String token = request.getHeaders().getFirst(authJwtProperties.getHeader());
// 如果前端设置了令牌前缀,则裁剪掉前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
{
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
}
return token;
}
//jwt鉴权失败处理类
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, ServerHttpResponse serverHttpResponse, ResponseCodeEnum responseCodeEnum) {
log.warn("token异常处理,请求路径:{}", exchange.getRequest().getPath());
serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
CommonResult<Object> responseResult = new CommonResult<>(responseCodeEnum.getCode(),responseCodeEnum.getMessage());
DataBuffer dataBuffer = serverHttpResponse.bufferFactory()
.wrap(JSON.toJSONStringWithDateFormat(responseResult, JSON.DEFFAULT_DATE_FORMAT)
.getBytes(StandardCharsets.UTF_8));
return serverHttpResponse.writeWith(Flux.just(dataBuffer));
}
}
关于统一返回类 CommonResult 和枚举类 ResponseCodeEnum,下面放出相关代码,可进行参考:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>{
private Integer code;
private String message;
private T data;
private int dataSize;
public CommonResult(Integer code,String message){
this(code,message,null,0);
}
public CommonResult(Integer code,String message,T data){
this(code,message,data,0);
}
}
public enum ResponseCodeEnum {
SUCCESS(200, "成功"),
FAIL(412, "失败"),
LOGIN_ERROR(202, "用户名或密码错误"),
UNKNOWN_ERROR(500, "未知错误"),
PARAMETER_ILLEGAL(400, "参数不合法"),
TOKEN_INVALID(412, "token 已过期或验证不正确!"),
TOKEN_SIGNATURE_INVALID(403, "无效的签名"),
TOKEN_MISSION(403, "token 缺失"),
REFRESH_TOKEN_INVALID(412, "refreshToken 无效"),
LOGOUT_ERROR(444, "用户登出失败");
private final int code;
private final String message;
ResponseCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
然后,启动上述两个微服务,即可进行登录认证:
redis中的token如下:
解析获得的token:
payload为我们添加的信息。
添加新的路由,启动一个其他业务微服务(比如cloud-address-manage),验证携带token访问的效果:
spring:
cloud:
gateway:
routes:
- id: route_auth # 认证微服务路由规则
uri: lb://cloud-auth-server # 负载均衡,将请求转发到注册中心注册的 auth 服务进行认证
predicates: # 断言
- Path=/api/auth/** # 如果前端请求路径包含 api/auth,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空
- id: route_address # 认证微服务路由规则
uri: lb://cloud-address-manage # 负载均衡,将请求转发到注册中心注册的 address 服务进行认证
predicates: # 断言
- Path=/api/address/** # 如果前端请求路径包含 api/address,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空
直接访问,显示未携带token(即未登录):
添加token后,访问成功:
微服务鉴权访问成功。
6.刷新令牌
当认证服务返回给客户端的 JWT 也就是 access_token 过期后,客户端如果需要再次通过发送登录请求重新拿到 access_token会使得用户体验很不友好。而JWT 生成后是不能篡改里面的内容,即使是 JWT 的有效期也不行。所以延长 access_token 有效期的做法并不适合,而且如果长期保持一个 access_token 有效,也是不安全的。所以我们时常使用refresh token来进行token的刷新。
我们一般会把 refresh_token 设置的过期时间稍微长一点,比如两倍于 access_token,当 access_token 过期后,refresh_token 如果还没有过期,就可以利用两者的过期时间差进行重新生成令牌的操作,也就是刷新令牌
,同时删除掉redis中缓存的旧令牌。
JWTTokenUtil中已有刷新令牌相关方法,下面进行cloud-auth-server中控制类刷新令牌代码的编写:
/**
* 刷新JWT令牌,用旧的令牌换新的令牌
* 参数为需要刷新的令牌
* header中携带刷新令牌
*/
@GetMapping("/token/refresh")
public CommonResult<Object> refreshToken(@RequestHeader(value = "${auth.jwt.header}") String token){
token = com.seven.springcloud.util.SecurityUtils.replaceTokenPrefix(token);
if (StringUtils.isEmpty(token)) {
return new CommonResult<>(ResponseCodeEnum.TOKEN_MISSION.getCode(),
ResponseCodeEnum.TOKEN_MISSION.getMessage());
}
// 对Token解签名,并验证Token是否过期
boolean isJwtNotValid = jwtTokenUtil.isTokenExpired(token);
if(isJwtNotValid){
return new CommonResult<>(ResponseCodeEnum.TOKEN_INVALID.getCode(),
ResponseCodeEnum.TOKEN_INVALID.getMessage());
}
// 验证 token 里面的 userId 是否为空
String userId = jwtTokenUtil.getUserIdFromToken(token);
String username = jwtTokenUtil.getUserNameFromToken(token);
if (StringUtils.isEmpty(userId)) {
return new CommonResult<>(ResponseCodeEnum.TOKEN_INVALID.getCode(),
ResponseCodeEnum.TOKEN_INVALID.getMessage());
}
// 这里为了保证 refreshToken 只能用一次,刷新后,会从 redis 中删除。
// 如果用的不是 redis 中的 refreshToken 进行刷新令牌,则不能刷新。
// 如果使用 redis 中已过期的 refreshToken 也不能刷新令牌。
boolean isRefreshTokenNotExisted = jwtTokenUtil.isRefreshTokenNotExistCache(token);
if(isRefreshTokenNotExisted){
return new CommonResult<>(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(),
ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage());
}
//String us = jwtTokenUtil.getUserIdFromToken(token);
Map<String, Object> tokenMap = jwtTokenUtil.refreshTokenAndGenerateToken(userId, username);
return new CommonResult<>(200, ResponseCodeEnum.SUCCESS.getMessage(),tokenMap);
}
访问时,参数为需要刷新的token,header中携带refresh token,验证refresh token通过,即可生成新的token值进行使用。