wip: jwt 刷新token临时提交
This commit is contained in:
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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 = "过期时间(单位:毫秒)")
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.system.model.dto;
|
||||
package com.youlai.boot.shared.websocket.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -80,8 +80,10 @@ security:
|
||||
jwt:
|
||||
# JWT 秘钥
|
||||
key: SecretKey012345678901234567890123456789012345678901234567890123456789
|
||||
# JWT 有效期(单位:秒)
|
||||
ttl: 7200
|
||||
# 访问令牌 有效期(单位:秒),默认 1 小时
|
||||
access-token-expiration: 3600
|
||||
# 刷新令牌有效期(单位:秒),默认 7 天
|
||||
refresh-token-expiration: 604800
|
||||
# 白名单列表
|
||||
ignore-urls:
|
||||
- /v3/api-docs/**
|
||||
|
||||
@@ -3,65 +3,9 @@ spring:
|
||||
name: youlai-boot
|
||||
profiles:
|
||||
active: dev
|
||||
config:
|
||||
import: classpath:codegen.yml
|
||||
|
||||
# 在 banner.txt 中显示项目版本,使用 @project.version@ 从 pom.xml 获取
|
||||
project:
|
||||
version: @project.version@
|
||||
|
||||
# 代码生成器配置
|
||||
codegen:
|
||||
# 下载代码文件名称
|
||||
downloadFileName: youlai-admin-code.zip
|
||||
# 后端项目名称
|
||||
backendAppName: youlai-boot
|
||||
# 前端项目名称
|
||||
frontendAppName: vue3-element-admin
|
||||
# 默认配置
|
||||
defaultConfig:
|
||||
author: youlaitech
|
||||
moduleName: system
|
||||
# 排除数据表
|
||||
excludeTables:
|
||||
- gen_config
|
||||
- gen_field_config
|
||||
## 模板配置
|
||||
templateConfigs:
|
||||
API:
|
||||
templatePath: codegen/api.ts.vm
|
||||
subpackageName: api
|
||||
extension: .ts
|
||||
VIEW:
|
||||
templatePath: codegen/index.vue.vm
|
||||
subpackageName: views
|
||||
extension: .vue
|
||||
Controller:
|
||||
templatePath: codegen/controller.java.vm
|
||||
subpackageName: controller
|
||||
Service:
|
||||
templatePath: codegen/service.java.vm
|
||||
subpackageName: service
|
||||
ServiceImpl:
|
||||
templatePath: codegen/serviceImpl.java.vm
|
||||
subpackageName: service.impl
|
||||
Mapper:
|
||||
templatePath: codegen/mapper.java.vm
|
||||
subpackageName: mapper
|
||||
MapperXml:
|
||||
templatePath: codegen/mapper.xml.vm
|
||||
subpackageName: mapper
|
||||
extension: .xml
|
||||
Converter:
|
||||
templatePath: codegen/converter.java.vm
|
||||
subpackageName: converter
|
||||
Query:
|
||||
templatePath: codegen/query.java.vm
|
||||
subpackageName: model.query
|
||||
Form:
|
||||
templatePath: codegen/form.java.vm
|
||||
subpackageName: model.form
|
||||
VO:
|
||||
templatePath: codegen/vo.java.vm
|
||||
subpackageName: model.vo
|
||||
Entity:
|
||||
templatePath: codegen/entity.java.vm
|
||||
subpackageName: model.entity
|
||||
|
||||
|
||||
|
||||
58
src/main/resources/codegen.yml
Normal file
58
src/main/resources/codegen.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
# 代码生成器配置
|
||||
codegen:
|
||||
# 下载代码文件名称
|
||||
downloadFileName: youlai-admin-code.zip
|
||||
# 后端项目名称
|
||||
backendAppName: youlai-boot
|
||||
# 前端项目名称
|
||||
frontendAppName: vue3-element-admin
|
||||
# 默认配置
|
||||
defaultConfig:
|
||||
author: youlaitech
|
||||
moduleName: system
|
||||
# 排除数据表
|
||||
excludeTables:
|
||||
- gen_config
|
||||
- gen_field_config
|
||||
## 模板配置
|
||||
templateConfigs:
|
||||
API:
|
||||
templatePath: codegen/api.ts.vm
|
||||
subpackageName: api
|
||||
extension: .ts
|
||||
VIEW:
|
||||
templatePath: codegen/index.vue.vm
|
||||
subpackageName: views
|
||||
extension: .vue
|
||||
Controller:
|
||||
templatePath: codegen/controller.java.vm
|
||||
subpackageName: controller
|
||||
Service:
|
||||
templatePath: codegen/service.java.vm
|
||||
subpackageName: service
|
||||
ServiceImpl:
|
||||
templatePath: codegen/serviceImpl.java.vm
|
||||
subpackageName: service.impl
|
||||
Mapper:
|
||||
templatePath: codegen/mapper.java.vm
|
||||
subpackageName: mapper
|
||||
MapperXml:
|
||||
templatePath: codegen/mapper.xml.vm
|
||||
subpackageName: mapper
|
||||
extension: .xml
|
||||
Converter:
|
||||
templatePath: codegen/converter.java.vm
|
||||
subpackageName: converter
|
||||
Query:
|
||||
templatePath: codegen/query.java.vm
|
||||
subpackageName: model.query
|
||||
Form:
|
||||
templatePath: codegen/form.java.vm
|
||||
subpackageName: model.form
|
||||
VO:
|
||||
templatePath: codegen/vo.java.vm
|
||||
subpackageName: model.vo
|
||||
Entity:
|
||||
templatePath: codegen/entity.java.vm
|
||||
subpackageName: model.entity
|
||||
Reference in New Issue
Block a user