From f3b7576bf1ef21825880aafe7ea68db53403680f Mon Sep 17 00:00:00 2001 From: zhangjf Date: Fri, 20 Feb 2026 11:23:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20Token=E8=AE=A4=E8=AF=81=E4=BB=8EJWT?= =?UTF-8?q?=E6=94=B9=E4=B8=BAUUID+Redis=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fund-common: 新增TokenInfo和TokenService类 - fund-sys: AuthServiceImpl改用TokenService,移除JwtUtil - fund-gateway: 新增TokenAuthFilter和ReactiveTokenService - 移除JWT依赖,支持主动登出和强制踢下线功能 --- .../fundplatform/common/auth/TokenInfo.java | 95 +++++++ .../common/auth/TokenService.java | 231 ++++++++++++++++++ fund-gateway/pom.xml | 21 +- .../gateway/filter/TenantGatewayFilter.java | 96 ++------ ...wtAuthFilter.java => TokenAuthFilter.java} | 83 +++---- .../gateway/service/ReactiveTokenService.java | 115 +++++++++ fund-sys/pom.xml | 19 -- .../sys/service/impl/AuthServiceImpl.java | 17 +- .../com/fundplatform/sys/utils/JwtUtil.java | 88 ------- 9 files changed, 503 insertions(+), 262 deletions(-) create mode 100644 fund-common/src/main/java/com/fundplatform/common/auth/TokenInfo.java create mode 100644 fund-common/src/main/java/com/fundplatform/common/auth/TokenService.java rename fund-gateway/src/main/java/com/fundplatform/gateway/filter/{JwtAuthFilter.java => TokenAuthFilter.java} (61%) create mode 100644 fund-gateway/src/main/java/com/fundplatform/gateway/service/ReactiveTokenService.java delete mode 100644 fund-sys/src/main/java/com/fundplatform/sys/utils/JwtUtil.java diff --git a/fund-common/src/main/java/com/fundplatform/common/auth/TokenInfo.java b/fund-common/src/main/java/com/fundplatform/common/auth/TokenInfo.java new file mode 100644 index 0000000..472f6ec --- /dev/null +++ b/fund-common/src/main/java/com/fundplatform/common/auth/TokenInfo.java @@ -0,0 +1,95 @@ +package com.fundplatform.common.auth; + +import java.io.Serializable; + +/** + * Token信息实体 + * 存储在Redis中的用户会话信息 + */ +public class TokenInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 登录时间戳 + */ + private Long loginTime; + + /** + * Token过期时间戳 + */ + private Long expireTime; + + public TokenInfo() { + } + + public TokenInfo(Long userId, String username, Long tenantId, Long expireTime) { + this.userId = userId; + this.username = username; + this.tenantId = tenantId; + this.loginTime = System.currentTimeMillis(); + this.expireTime = expireTime; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getLoginTime() { + return loginTime; + } + + public void setLoginTime(Long loginTime) { + this.loginTime = loginTime; + } + + public Long getExpireTime() { + return expireTime; + } + + public void setExpireTime(Long expireTime) { + this.expireTime = expireTime; + } + + /** + * 检查Token是否过期 + */ + public boolean isExpired() { + return System.currentTimeMillis() > expireTime; + } +} diff --git a/fund-common/src/main/java/com/fundplatform/common/auth/TokenService.java b/fund-common/src/main/java/com/fundplatform/common/auth/TokenService.java new file mode 100644 index 0000000..15b86fd --- /dev/null +++ b/fund-common/src/main/java/com/fundplatform/common/auth/TokenService.java @@ -0,0 +1,231 @@ +package com.fundplatform.common.auth; + +import com.fundplatform.common.cache.RedisService; +import org.springframework.stereotype.Service; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Token服务 - 基于UUID + Redis实现 + * 替代JWT方案,提供更好的token控制能力 + */ +@Service +public class TokenService { + + private final RedisService redisService; + + /** + * Token缓存Key前缀 + */ + private static final String TOKEN_KEY_PREFIX = "auth:token:"; + + /** + * 用户Token列表Key前缀(用于支持单用户多设备登录控制) + */ + private static final String USER_TOKENS_PREFIX = "auth:user:tokens:"; + + /** + * 默认Token过期时间:24小时 + */ + private static final long DEFAULT_EXPIRE_SECONDS = 24 * 60 * 60; + + public TokenService(RedisService redisService) { + this.redisService = redisService; + } + + /** + * 生成Token + * + * @param userId 用户ID + * @param username 用户名 + * @param tenantId 租户ID + * @return Token字符串 + */ + public String generateToken(Long userId, String username, Long tenantId) { + return generateToken(userId, username, tenantId, DEFAULT_EXPIRE_SECONDS); + } + + /** + * 生成Token(指定过期时间) + * + * @param userId 用户ID + * @param username 用户名 + * @param tenantId 租户ID + * @param expireSeconds 过期时间(秒) + * @return Token字符串 + */ + public String generateToken(Long userId, String username, Long tenantId, long expireSeconds) { + // 生成UUID作为Token + String token = UUID.randomUUID().toString().replace("-", ""); + + // 计算过期时间戳 + long expireTime = System.currentTimeMillis() + expireSeconds * 1000; + + // 创建Token信息 + TokenInfo tokenInfo = new TokenInfo(userId, username, tenantId, expireTime); + + // 存储到Redis + String tokenKey = getTokenKey(token); + redisService.setEx(tokenKey, tokenInfo, expireSeconds); + + // 同时存储用户Token列表(用于管理用户所有登录会话) + String userTokensKey = getUserTokensKey(userId); + redisService.hSet(userTokensKey, token, expireTime); + redisService.expire(userTokensKey, expireSeconds, TimeUnit.SECONDS); + + return token; + } + + /** + * 验证Token并获取用户信息 + * + * @param token Token字符串 + * @return TokenInfo,如果无效返回null + */ + public TokenInfo validateToken(String token) { + if (token == null || token.isEmpty()) { + return null; + } + + String tokenKey = getTokenKey(token); + TokenInfo tokenInfo = redisService.get(tokenKey, TokenInfo.class); + + if (tokenInfo == null) { + return null; + } + + // 检查是否过期 + if (tokenInfo.isExpired()) { + // 清除过期Token + deleteToken(token); + return null; + } + + return tokenInfo; + } + + /** + * 刷新Token过期时间 + * + * @param token Token字符串 + * @return 是否成功 + */ + public boolean refreshToken(String token) { + return refreshToken(token, DEFAULT_EXPIRE_SECONDS); + } + + /** + * 刷新Token过期时间(指定过期时间) + * + * @param token Token字符串 + * @param expireSeconds 过期时间(秒) + * @return 是否成功 + */ + public boolean refreshToken(String token, long expireSeconds) { + TokenInfo tokenInfo = validateToken(token); + if (tokenInfo == null) { + return false; + } + + // 更新过期时间 + long expireTime = System.currentTimeMillis() + expireSeconds * 1000; + tokenInfo.setExpireTime(expireTime); + + // 更新Redis中的Token + String tokenKey = getTokenKey(token); + redisService.setEx(tokenKey, tokenInfo, expireSeconds); + + // 更新用户Token列表 + String userTokensKey = getUserTokensKey(tokenInfo.getUserId()); + redisService.hSet(userTokensKey, token, expireTime); + redisService.expire(userTokensKey, expireSeconds, TimeUnit.SECONDS); + + return true; + } + + /** + * 删除Token(登出) + * + * @param token Token字符串 + */ + public void deleteToken(String token) { + if (token == null || token.isEmpty()) { + return; + } + + // 获取Token信息以移除用户Token列表 + String tokenKey = getTokenKey(token); + TokenInfo tokenInfo = redisService.get(tokenKey, TokenInfo.class); + + // 删除Token + redisService.delete(tokenKey); + + // 从用户Token列表中移除 + if (tokenInfo != null) { + String userTokensKey = getUserTokensKey(tokenInfo.getUserId()); + redisService.hDelete(userTokensKey, token); + } + } + + /** + * 删除用户所有Token(强制登出所有设备) + * + * @param userId 用户ID + */ + public void deleteAllUserTokens(Long userId) { + String userTokensKey = getUserTokensKey(userId); + java.util.Map tokens = redisService.hGetAll(userTokensKey); + + if (tokens != null && !tokens.isEmpty()) { + // 删除所有Token + for (Object token : tokens.keySet()) { + redisService.delete(getTokenKey((String) token)); + } + } + + // 删除用户Token列表 + redisService.delete(userTokensKey); + } + + /** + * 检查Token是否存在 + * + * @param token Token字符串 + * @return 是否存在 + */ + public boolean existsToken(String token) { + if (token == null || token.isEmpty()) { + return false; + } + return redisService.hasKey(getTokenKey(token)); + } + + /** + * 获取Token剩余有效时间(秒) + * + * @param token Token字符串 + * @return 剩余秒数,-1表示不存在或已过期 + */ + public long getTokenTTL(String token) { + if (token == null || token.isEmpty()) { + return -1; + } + Long ttl = redisService.getExpire(getTokenKey(token)); + return ttl != null ? ttl : -1; + } + + /** + * 构建Token存储Key + */ + private String getTokenKey(String token) { + return TOKEN_KEY_PREFIX + token; + } + + /** + * 构建用户Token列表Key + */ + private String getUserTokensKey(Long userId) { + return USER_TOKENS_PREFIX + userId; + } +} diff --git a/fund-gateway/pom.xml b/fund-gateway/pom.xml index 9df25f4..74bbdca 100644 --- a/fund-gateway/pom.xml +++ b/fund-gateway/pom.xml @@ -35,26 +35,7 @@ 4.0.0 - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - runtime - - - + org.springframework.boot spring-boot-starter-data-redis-reactive diff --git a/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TenantGatewayFilter.java b/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TenantGatewayFilter.java index eab12bb..7cb3c85 100644 --- a/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TenantGatewayFilter.java +++ b/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TenantGatewayFilter.java @@ -1,37 +1,30 @@ package com.fundplatform.gateway.filter; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; /** * 租户信息全局过滤器 * - *

从 JWT Token 中提取租户 ID,并写入请求头,供下游服务使用

+ *

从请求头中获取用户信息(由TokenAuthFilter解析),构建租户组信息

+ *

在TokenAuthFilter之后执行,用于多租户路由

*/ @Component public class TenantGatewayFilter implements GlobalFilter, Ordered { private static final Logger logger = LoggerFactory.getLogger(TenantGatewayFilter.class); - private static final String SECRET_KEY = "fundplatform-secret-key-for-jwt-token-generation-min-256-bits"; - private static final String TOKEN_PREFIX = "Bearer "; - // Header 名称 + // Header 名称(由TokenAuthFilter设置) public static final String HEADER_TENANT_ID = "X-Tenant-Id"; public static final String HEADER_TENANT_GROUP = "X-Tenant-Group"; public static final String HEADER_USER_ID = "X-User-Id"; @@ -53,44 +46,23 @@ public class TenantGatewayFilter implements GlobalFilter, Ordered { return chain.filter(exchange); } - // 获取 Token - String token = getToken(request); - if (token == null) { - logger.warn("缺少 Token: {}", path); - return chain.filter(exchange); - } + // 从请求头获取用户信息(由TokenAuthFilter设置) + String tenantId = request.getHeaders().getFirst(HEADER_TENANT_ID); + String userId = request.getHeaders().getFirst(HEADER_USER_ID); + String username = request.getHeaders().getFirst(HEADER_USERNAME); - try { - // 解析 Token - Claims claims = validateToken(token); - if (claims == null) { - logger.warn("Token 无效:{}", path); - return chain.filter(exchange); - } - - // 提取租户信息和用户信息 - String tenantId = claims.get("tenantId", String.class); - Long userId = claims.get("userId", Long.class); - String username = claims.get("username", String.class); - - // 将租户信息和用户信息写入请求头 - ServerHttpRequest mutatedRequest = request.mutate() - .header(HEADER_TENANT_ID, tenantId != null ? tenantId : "1") - .header(HEADER_TENANT_GROUP, buildTenantGroup(tenantId != null ? tenantId : "1")) - .header(HEADER_USER_ID, userId != null ? String.valueOf(userId) : "") - .header(HEADER_USERNAME, username != null ? username : "") - .build(); - - logger.debug("[TenantGateway] 租户ID: {}, 租户组:{}, 用户:{}, 路径:{}", - tenantId, buildTenantGroup(tenantId != null ? tenantId : "1"), - username, path); - - return chain.filter(exchange.mutate().request(mutatedRequest).build()); - - } catch (Exception e) { - logger.error("租户信息提取失败:{}", e.getMessage()); - return chain.filter(exchange); - } + // 构建租户组信息 + String tenantGroup = buildTenantGroup(tenantId); + + // 将租户组信息写入请求头 + ServerHttpRequest mutatedRequest = request.mutate() + .header(HEADER_TENANT_GROUP, tenantGroup) + .build(); + + logger.debug("[TenantGateway] 租户ID: {}, 租户组: {}, 用户: {}, 路径: {}", + tenantId, tenantGroup, username, path); + + return chain.filter(exchange.mutate().request(mutatedRequest).build()); } /** @@ -100,34 +72,6 @@ public class TenantGatewayFilter implements GlobalFilter, Ordered { return WHITE_LIST.stream().anyMatch(path::startsWith); } - /** - * 从请求头获取 Token - */ - private String getToken(ServerHttpRequest request) { - String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); - if (authHeader != null && authHeader.startsWith(TOKEN_PREFIX)) { - return authHeader.substring(TOKEN_PREFIX.length()); - } - return null; - } - - /** - * 验证 Token - */ - private Claims validateToken(String token) { - try { - SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)); - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - logger.error("Token 解析失败:{}", e.getMessage()); - return null; - } - } - /** * 构建租户组名称 */ @@ -140,6 +84,6 @@ public class TenantGatewayFilter implements GlobalFilter, Ordered { @Override public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE + 2; // 在 JwtAuthFilter 之后 + return Ordered.HIGHEST_PRECEDENCE + 2; // 在TokenAuthFilter之后执行 } } diff --git a/fund-gateway/src/main/java/com/fundplatform/gateway/filter/JwtAuthFilter.java b/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TokenAuthFilter.java similarity index 61% rename from fund-gateway/src/main/java/com/fundplatform/gateway/filter/JwtAuthFilter.java rename to fund-gateway/src/main/java/com/fundplatform/gateway/filter/TokenAuthFilter.java index 4a7d027..a8ea472 100644 --- a/fund-gateway/src/main/java/com/fundplatform/gateway/filter/JwtAuthFilter.java +++ b/fund-gateway/src/main/java/com/fundplatform/gateway/filter/TokenAuthFilter.java @@ -2,13 +2,11 @@ package com.fundplatform.gateway.filter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fundplatform.common.auth.TokenInfo; import com.fundplatform.common.core.Result; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; +import com.fundplatform.gateway.service.ReactiveTokenService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; @@ -22,20 +20,19 @@ import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; /** - * JWT鉴权过滤器 - * 验证JWT Token,提取用户信息 + * Token鉴权过滤器 + * 基于UUID + Redis实现,替代JWT方案 */ @Component -public class JwtAuthFilter implements GlobalFilter, Ordered { +public class TokenAuthFilter implements GlobalFilter, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(TokenAuthFilter.class); - private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class); - private static final String SECRET_KEY = "fundplatform-secret-key-for-jwt-token-generation-min-256-bits"; private static final String TOKEN_PREFIX = "Bearer "; private static final String USER_ID_HEADER = "X-User-Id"; private static final String USERNAME_HEADER = "X-Username"; @@ -49,7 +46,13 @@ public class JwtAuthFilter implements GlobalFilter, Ordered { "/proj/api/v1/proj/health" ); - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ReactiveTokenService reactiveTokenService; + private final ObjectMapper objectMapper; + + public TokenAuthFilter(ReactiveTokenService reactiveTokenService) { + this.reactiveTokenService = reactiveTokenService; + this.objectMapper = new ObjectMapper(); + } @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { @@ -67,33 +70,26 @@ public class JwtAuthFilter implements GlobalFilter, Ordered { return unauthorized(exchange, "缺少认证Token"); } - try { - // 验证Token - Claims claims = validateToken(token); - if (claims == null) { - return unauthorized(exchange, "Token无效或已过期"); - } + // 使用响应式方式验证Token + return reactiveTokenService.validateToken(token) + .flatMap(tokenInfo -> { + if (tokenInfo == null) { + return unauthorized(exchange, "Token无效或已过期"); + } - // 提取用户信息 - Long userId = claims.get("userId", Long.class); - String username = claims.get("username", String.class); - Long tenantId = claims.get("tenantId", Long.class); + // 将用户信息写入请求头 + ServerHttpRequest mutatedRequest = request.mutate() + .header(USER_ID_HEADER, String.valueOf(tokenInfo.getUserId())) + .header(USERNAME_HEADER, tokenInfo.getUsername()) + .header(TENANT_ID_HEADER, String.valueOf(tokenInfo.getTenantId())) + .build(); - // 将用户信息写入请求头 - ServerHttpRequest mutatedRequest = request.mutate() - .header(USER_ID_HEADER, String.valueOf(userId)) - .header(USERNAME_HEADER, username) - .header(TENANT_ID_HEADER, String.valueOf(tenantId)) - .build(); + logger.debug("Token验证通过: userId={}, username={}, tenantId={}", + tokenInfo.getUserId(), tokenInfo.getUsername(), tokenInfo.getTenantId()); - logger.debug("Token验证通过: userId={}, username={}, tenantId={}", userId, username, tenantId); - - return chain.filter(exchange.mutate().request(mutatedRequest).build()); - - } catch (Exception e) { - logger.error("Token验证失败: {}", e.getMessage()); - return unauthorized(exchange, "Token验证失败: " + e.getMessage()); - } + return chain.filter(exchange.mutate().request(mutatedRequest).build()); + }) + .switchIfEmpty(unauthorized(exchange, "Token无效或已过期")); } /** @@ -114,23 +110,6 @@ public class JwtAuthFilter implements GlobalFilter, Ordered { return null; } - /** - * 验证Token - */ - private Claims validateToken(String token) { - try { - SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)); - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - logger.error("Token解析失败: {}", e.getMessage()); - return null; - } - } - /** * 返回未授权响应 */ diff --git a/fund-gateway/src/main/java/com/fundplatform/gateway/service/ReactiveTokenService.java b/fund-gateway/src/main/java/com/fundplatform/gateway/service/ReactiveTokenService.java new file mode 100644 index 0000000..d1676a4 --- /dev/null +++ b/fund-gateway/src/main/java/com/fundplatform/gateway/service/ReactiveTokenService.java @@ -0,0 +1,115 @@ +package com.fundplatform.gateway.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fundplatform.common.auth.TokenInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.ReactiveRedisTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +/** + * 响应式Token服务 + * 用于Gateway的Token验证 + */ +@Service +public class ReactiveTokenService { + + private static final Logger logger = LoggerFactory.getLogger(ReactiveTokenService.class); + + private final ReactiveRedisTemplate reactiveRedisTemplate; + private final ObjectMapper objectMapper; + + /** + * Token缓存Key前缀 + */ + private static final String TOKEN_KEY_PREFIX = "auth:token:"; + + public ReactiveTokenService(ReactiveRedisTemplate reactiveRedisTemplate) { + this.reactiveRedisTemplate = reactiveRedisTemplate; + this.objectMapper = new ObjectMapper(); + } + + /** + * 验证Token并获取用户信息 + * + * @param token Token字符串 + * @return Mono,如果无效返回Mono.empty() + */ + public Mono validateToken(String token) { + if (token == null || token.isEmpty()) { + return Mono.empty(); + } + + String tokenKey = getTokenKey(token); + + return reactiveRedisTemplate.opsForValue() + .get(tokenKey) + .map(this::convertToTokenInfo) + .flatMap(tokenInfo -> { + if (tokenInfo == null || tokenInfo.isExpired()) { + // Token过期,删除 + return reactiveRedisTemplate.delete(tokenKey) + .then(Mono.empty()); + } + return Mono.just(tokenInfo); + }) + .onErrorResume(e -> { + logger.error("Token验证失败: {}", e.getMessage()); + return Mono.empty(); + }); + } + + /** + * 检查Token是否存在 + * + * @param token Token字符串 + * @return Mono + */ + public Mono existsToken(String token) { + if (token == null || token.isEmpty()) { + return Mono.just(false); + } + return reactiveRedisTemplate.hasKey(getTokenKey(token)); + } + + /** + * 删除Token + * + * @param token Token字符串 + * @return Mono + */ + public Mono deleteToken(String token) { + if (token == null || token.isEmpty()) { + return Mono.just(false); + } + return reactiveRedisTemplate.delete(getTokenKey(token)) + .map(count -> count > 0) + .defaultIfEmpty(false); + } + + /** + * 构建Token存储Key + */ + private String getTokenKey(String token) { + return TOKEN_KEY_PREFIX + token; + } + + /** + * 将Redis中的对象转换为TokenInfo + */ + private TokenInfo convertToTokenInfo(Object obj) { + try { + if (obj instanceof TokenInfo) { + return (TokenInfo) obj; + } + // 尝试Jackson转换 + return objectMapper.convertValue(obj, TokenInfo.class); + } catch (Exception e) { + logger.error("TokenInfo转换失败: {}", e.getMessage()); + return null; + } + } +} diff --git a/fund-sys/pom.xml b/fund-sys/pom.xml index 3065312..279e0b8 100644 --- a/fund-sys/pom.xml +++ b/fund-sys/pom.xml @@ -75,25 +75,6 @@
--> - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - runtime - - org.springframework.security diff --git a/fund-sys/src/main/java/com/fundplatform/sys/service/impl/AuthServiceImpl.java b/fund-sys/src/main/java/com/fundplatform/sys/service/impl/AuthServiceImpl.java index 0ea2de5..a5fdec5 100644 --- a/fund-sys/src/main/java/com/fundplatform/sys/service/impl/AuthServiceImpl.java +++ b/fund-sys/src/main/java/com/fundplatform/sys/service/impl/AuthServiceImpl.java @@ -1,11 +1,11 @@ package com.fundplatform.sys.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fundplatform.common.auth.TokenService; import com.fundplatform.sys.data.entity.SysUser; import com.fundplatform.sys.data.service.SysUserDataService; import com.fundplatform.sys.dto.LoginRequestDTO; import com.fundplatform.sys.service.AuthService; -import com.fundplatform.sys.utils.JwtUtil; import com.fundplatform.sys.vo.LoginVO; import com.fundplatform.sys.vo.UserVO; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -18,10 +18,12 @@ import org.springframework.stereotype.Service; public class AuthServiceImpl implements AuthService { private final SysUserDataService userDataService; + private final TokenService tokenService; private final BCryptPasswordEncoder passwordEncoder; - public AuthServiceImpl(SysUserDataService userDataService) { + public AuthServiceImpl(SysUserDataService userDataService, TokenService tokenService) { this.userDataService = userDataService; + this.tokenService = tokenService; this.passwordEncoder = new BCryptPasswordEncoder(); } @@ -51,8 +53,8 @@ public class AuthServiceImpl implements AuthService { throw new RuntimeException("用户已被禁用"); } - // 生成Token - String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getTenantId()); + // 使用UUID + Redis生成Token + String token = tokenService.generateToken(user.getId(), user.getUsername(), user.getTenantId()); // 返回登录信息 return new LoginVO(user.getId(), user.getUsername(), token, user.getTenantId()); @@ -60,8 +62,9 @@ public class AuthServiceImpl implements AuthService { @Override public void logout(Long userId) { - // TODO: 可以在此处清除用户的Token缓存或记录登出日志 - // 目前JWT是无状态的,登出只需前端清除Token即可 + // 清除用户所有Token(强制登出所有设备) + // 如果只需要登出当前设备,需要从前端传递token + tokenService.deleteAllUserTokens(userId); } @Override @@ -77,7 +80,7 @@ public class AuthServiceImpl implements AuthService { } // 生成新Token - String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getTenantId()); + String token = tokenService.generateToken(user.getId(), user.getUsername(), user.getTenantId()); return new LoginVO(user.getId(), user.getUsername(), token, user.getTenantId()); } diff --git a/fund-sys/src/main/java/com/fundplatform/sys/utils/JwtUtil.java b/fund-sys/src/main/java/com/fundplatform/sys/utils/JwtUtil.java deleted file mode 100644 index 19d791e..0000000 --- a/fund-sys/src/main/java/com/fundplatform/sys/utils/JwtUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.fundplatform.sys.utils; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - * JWT工具类 - */ -public class JwtUtil { - - private static final String SECRET_KEY = "fundplatform-secret-key-for-jwt-token-generation-min-256-bits"; - private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 24小时 - - private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)); - - /** - * 生成Token - */ - public static String generateToken(Long userId, String username, Long tenantId) { - Map claims = new HashMap<>(); - claims.put("userId", userId); - claims.put("username", username); - claims.put("tenantId", tenantId); - - return Jwts.builder() - .setClaims(claims) - .setSubject(username) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) - .signWith(KEY, SignatureAlgorithm.HS256) - .compact(); - } - - /** - * 解析Token - */ - public static Claims parseToken(String token) { - return Jwts.parserBuilder() - .setSigningKey(KEY) - .build() - .parseClaimsJws(token) - .getBody(); - } - - /** - * 验证Token - */ - public static boolean validateToken(String token) { - try { - Claims claims = parseToken(token); - return claims.getExpiration().after(new Date()); - } catch (Exception e) { - return false; - } - } - - /** - * 从Token获取用户ID - */ - public static Long getUserId(String token) { - Claims claims = parseToken(token); - return claims.get("userId", Long.class); - } - - /** - * 从Token获取用户名 - */ - public static String getUsername(String token) { - Claims claims = parseToken(token); - return claims.get("username", String.class); - } - - /** - * 从Token获取租户ID - */ - public static Long getTenantId(String token) { - Claims claims = parseToken(token); - return claims.get("tenantId", Long.class); - } -}