wip: jwt 刷新token临时提交

This commit is contained in:
haoxr
2024-11-12 10:40:28 +08:00
parent cb78ea2cfb
commit b4397b13b0
19 changed files with 209 additions and 122 deletions

View File

@@ -6,6 +6,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 安全配置属性
*
* @author haoxr
* @since 2024/4/18
*/
@@ -36,9 +38,14 @@ public class SecurityProperties {
private String key;
/**
* JWT 过期时间
* 访问令牌有效期(单位:秒)
*/
private Long ttl;
private Integer accessTokenExpiration;
/**
* 刷新令牌有效期(单位:秒)
*/
private Integer refreshTokenExpiration;
}
}

View File

@@ -47,9 +47,14 @@ public class JwtUtils {
/**
* JWT Token 的有效时间(单位:秒)
* 访问令牌过期时间单位:秒
*/
private static int ttl;
private static int accessTokenExpiration;
/**
* 刷新令牌过期时间,单位:秒
*/
private static int refreshTokenExpiration;
@Value("${security.jwt.key}")
@@ -57,18 +62,38 @@ public class JwtUtils {
JwtUtils.key = key.getBytes();
}
@Value("${security.jwt.ttl}")
public void setTtl(Integer ttl) {
JwtUtils.ttl = ttl;
@Value("${security.jwt.access-token-expiration}")
public void setAccessTokenExpiration(Integer accessTokenExpiration) {
JwtUtils.accessTokenExpiration = accessTokenExpiration;
}
@Value("${security.jwt.refresh-token-expiration}")
public void setRefreshTokenExpiration(Integer refreshTokenExpiration) {
JwtUtils.refreshTokenExpiration = refreshTokenExpiration;
}
/**
* 生成访问令牌JWT Token
*
* @param authentication 用户认证信息
* @return Token 字符串
*/
public static String createAccessToken(Authentication authentication) {
return createToken(authentication, accessTokenExpiration);
}
public static String createRefreshToken(Authentication authentication) {
return createToken(authentication, refreshTokenExpiration);
}
/**
* 生成 JWT Token
*
* @param authentication 用户认证信息
* @return Token 字符串
*/
public static String createToken(Authentication authentication) {
public static String createToken(Authentication authentication, int expiration) {
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
@@ -78,7 +103,7 @@ public class JwtUtils {
payload.put(JwtClaimConstants.DATA_SCOPE, userDetails.getDataScope()); // 数据权限范围
// claims 中添加角色信息
Set<String> roles = userDetails.getAuthorities().stream()
Set<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
payload.put(JwtClaimConstants.AUTHORITIES, roles);
@@ -87,9 +112,9 @@ public class JwtUtils {
payload.put(JWTPayload.ISSUED_AT, now);
// 设置过期时间 -1 表示永不过期
if (ttl != -1) {
Date expiration = DateUtil.offsetSecond(now, ttl);
payload.put(JWTPayload.EXPIRES_AT, expiration);
if (expiration != -1) {
Date expiresAt = DateUtil.offsetSecond(now, expiration);
payload.put(JWTPayload.EXPIRES_AT, expiresAt);
}
payload.put(JWTPayload.SUBJECT, authentication.getName());
payload.put(JWTPayload.JWT_ID, IdUtil.simpleUUID());

View File

@@ -2,9 +2,10 @@ package com.youlai.boot.shared.auth.controller;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.system.model.dto.CaptchaResult;
import com.youlai.boot.system.model.dto.LoginResult;
import com.youlai.boot.shared.auth.model.CaptchaResult;
import com.youlai.boot.shared.auth.model.LoginResult;
import com.youlai.boot.common.annotation.Log;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -53,4 +54,11 @@ public class AuthController {
CaptchaResult captcha = authService.getCaptcha();
return Result.success(captcha);
}
@Operation(summary = "刷新token")
@PostMapping("/refresh-token")
public Result<?> refreshToken(@RequestBody RefreshTokenRequest request) {
LoginResult loginResult = authService.refreshToken(request);
return Result.success(loginResult);
}
}

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.system.model.dto;
package com.youlai.boot.shared.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.system.model.dto;
package com.youlai.boot.shared.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
@@ -9,13 +9,13 @@ import lombok.Data;
@Builder
public class LoginResult {
@Schema(description = "访问token")
@Schema(description = "访问令牌")
private String accessToken;
@Schema(description = "token 类型",example = "Bearer")
private String tokenType;
@Schema(description = "刷新token")
@Schema(description = "刷新令牌")
private String refreshToken;
@Schema(description = "过期时间(单位:毫秒)")

View File

@@ -0,0 +1,21 @@
package com.youlai.boot.shared.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 刷新令牌请求参数
*
* @author haoxr
* @since 2024/11/11
*/
@Schema(description = "刷新令牌请求参数")
@Data
public class RefreshTokenRequest {
@Schema(description = "刷新令牌")
@NotBlank(message = "刷新令牌不能为空")
private String refreshToken;
}

View File

@@ -1,7 +1,8 @@
package com.youlai.boot.shared.auth.service;
import com.youlai.boot.system.model.dto.CaptchaResult;
import com.youlai.boot.system.model.dto.LoginResult;
import com.youlai.boot.shared.auth.model.CaptchaResult;
import com.youlai.boot.shared.auth.model.LoginResult;
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
/**
* 认证服务接口
@@ -31,4 +32,12 @@ public interface AuthService {
* @return 验证码
*/
CaptchaResult getCaptcha();
/**
* 刷新令牌
*
* @param request 刷新令牌请求参数
* @return 登录结果
*/
LoginResult refreshToken(RefreshTokenRequest request);
}

View File

@@ -5,12 +5,19 @@ import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.config.property.SecurityProperties;
import com.youlai.boot.core.security.util.SecurityUtils;
import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum;
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.system.model.dto.CaptchaResult;
import com.youlai.boot.system.model.dto.LoginResult;
import com.youlai.boot.shared.auth.model.CaptchaResult;
import com.youlai.boot.shared.auth.model.LoginResult;
import com.youlai.boot.config.property.CaptchaProperties;
import com.youlai.boot.core.security.util.JwtUtils;
import lombok.RequiredArgsConstructor;
@@ -41,6 +48,7 @@ public class AuthServiceImpl implements AuthService {
private final CodeGenerator codeGenerator;
private final Font captchaFont;
private final CaptchaProperties captchaProperties;
private final SecurityProperties securityProperties;
/**
* 登录
@@ -54,16 +62,18 @@ public class AuthServiceImpl implements AuthService {
// 创建认证令牌对象
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username.toLowerCase().trim(), password);
// 执行用户认证
// 执行用户认证认证成功返回的Authentication是SysUserDetailsService#loadUserByUsername获取到的的UserDetails
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 认证成功后生成JWT令牌
String accessToken = JwtUtils.createToken(authentication);
String accessToken = JwtUtils.createAccessToken(authentication);
String refreshToken = JwtUtils.createRefreshToken(authentication);
// 将认证信息存入Security上下文便于在AOP如日志记录中获取当前用户信息
SecurityContextHolder.getContext().setAuthentication(authentication);
// 返回包含JWT令牌的登录结果
return LoginResult.builder()
.tokenType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
@@ -122,4 +132,36 @@ public class AuthServiceImpl implements AuthService {
.build();
}
/**
* 刷新令牌
*
* @param request 刷新令牌请求参数
* @return 新的访问令牌
*/
@Override
public LoginResult refreshToken(RefreshTokenRequest request) {
// 验证刷新令牌
String refreshToken = request.getRefreshToken();
JWT jwt = JWTUtil.parseToken(refreshToken);
boolean isValidate = jwt.setKey(securityProperties.getJwt().getKey().getBytes()).validate(0);
if (!isValidate || redisTemplate.hasKey(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jwt.getPayloads().getStr(JWTPayload.JWT_ID))) {
throw new BusinessException(ResultCode.TOKEN_INVALID);
}
Authentication authentication = JwtUtils.getAuthentication(jwt.getPayloads());
// 创建新的访问令牌
String newAccessToken = JwtUtils.createAccessToken(authentication);
// 返回新的访问令牌
return LoginResult.builder()
.tokenType("Bearer")
.accessToken(newAccessToken)
.refreshToken(refreshToken) // 保持刷新令牌不变
.build();
}
}

View File

@@ -2,7 +2,7 @@ package com.youlai.boot.shared.file.controller;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.shared.file.service.FileService;
import com.youlai.boot.system.model.dto.FileInfo;
import com.youlai.boot.shared.file.model.FileInfo;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.system.model.dto;
package com.youlai.boot.shared.file.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,6 +1,6 @@
package com.youlai.boot.shared.file.service;
import com.youlai.boot.system.model.dto.FileInfo;
import com.youlai.boot.shared.file.model.FileInfo;
import org.springframework.web.multipart.MultipartFile;
/**

View File

@@ -9,7 +9,7 @@ import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.youlai.boot.shared.file.service.FileService;
import com.youlai.boot.system.model.dto.FileInfo;
import com.youlai.boot.shared.file.model.FileInfo;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.RequiredArgsConstructor;

View File

@@ -6,7 +6,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.shared.file.service.FileService;
import com.youlai.boot.system.model.dto.FileInfo;
import com.youlai.boot.shared.file.model.FileInfo;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;

View File

@@ -1,6 +1,6 @@
package com.youlai.boot.shared.websocket.controller;
import com.youlai.boot.system.model.dto.ChatMessage;
import com.youlai.boot.shared.websocket.model.ChatMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.system.model.dto;
package com.youlai.boot.shared.websocket.model;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

@@ -1,29 +0,0 @@
package com.youlai.boot.system.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Set;
/**
* 消息载体
*
* @author Theo
* @since 2024-9-2 14:32:58
* @version 1.0.0
*/
@Data
public class MessageDTO {
@Schema(description = "消息内容")
private String content;
@Schema(description = "发送者")
private String sender;
@Schema(description = "接收者")
private Set<String> receivers;
}