From a03cc14749fb60b63773481246cc511f0b31c039 Mon Sep 17 00:00:00 2001 From: litianxiang Date: Wed, 22 Apr 2026 11:15:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E6=9C=8D=E5=8A=A1=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityProperties.java | 21 --- .../filter/SellerAuthenticationFilter.java | 134 ------------------ .../common/security/jwt/SellerJwtHelper.java | 67 --------- .../security/utils/SellerLocalCacheUtils.java | 57 -------- .../security/utils/SellerRedisUtil.java | 40 ------ .../com/aida/seller/config/FilterConfig.java | 20 --- .../com/aida/seller/config/WebConfig.java | 16 ++- .../controller/DesignerController.java | 8 -- .../java/com/aida/seller/util/JwtUtil.java | 99 ------------- src/main/resources/application.yml | 65 ++------- src/main/resources/bootstrap.yml | 16 ++- 11 files changed, 39 insertions(+), 504 deletions(-) delete mode 100644 src/main/java/com/aida/seller/common/security/config/SecurityProperties.java delete mode 100644 src/main/java/com/aida/seller/common/security/filter/SellerAuthenticationFilter.java delete mode 100644 src/main/java/com/aida/seller/common/security/jwt/SellerJwtHelper.java delete mode 100644 src/main/java/com/aida/seller/common/security/utils/SellerLocalCacheUtils.java delete mode 100644 src/main/java/com/aida/seller/common/security/utils/SellerRedisUtil.java delete mode 100644 src/main/java/com/aida/seller/config/FilterConfig.java delete mode 100644 src/main/java/com/aida/seller/util/JwtUtil.java diff --git a/src/main/java/com/aida/seller/common/security/config/SecurityProperties.java b/src/main/java/com/aida/seller/common/security/config/SecurityProperties.java deleted file mode 100644 index b36e6bf..0000000 --- a/src/main/java/com/aida/seller/common/security/config/SecurityProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.aida.seller.common.security.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Data -@Configuration -@ConfigurationProperties(prefix = "spring.security") -public class SecurityProperties { - - private String jwtSecret; - - private String jwtTokenHeader = "Authorization"; - - private String jwtTokenPrefix = "Bearer-"; - - private long jwtExpiration; - - private String[] ignorePaths; -} diff --git a/src/main/java/com/aida/seller/common/security/filter/SellerAuthenticationFilter.java b/src/main/java/com/aida/seller/common/security/filter/SellerAuthenticationFilter.java deleted file mode 100644 index ab89633..0000000 --- a/src/main/java/com/aida/seller/common/security/filter/SellerAuthenticationFilter.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.aida.seller.common.security.filter; - -import cn.hutool.core.util.StrUtil; -import com.aida.seller.common.context.UserContext; -import com.aida.seller.common.security.config.SecurityProperties; -import com.aida.seller.common.security.jwt.SellerJwtHelper; -import com.aida.seller.common.security.utils.SellerRedisUtil; -import com.aida.seller.model.vo.AuthPrincipalVo; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import com.aida.seller.common.security.utils.SellerLocalCacheUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -@Slf4j -@Component -@RequiredArgsConstructor -public class SellerAuthenticationFilter extends OncePerRequestFilter { - - private final SellerJwtHelper jwtHelper; - private final SecurityProperties securityProperties; - private final SellerRedisUtil redisUtil; - - private static final List FILTER_URL = Arrays.asList( - "/favicon.ico", "/doc.html", "/swagger-ui.html", - "/swagger-resources", "/swagger-resources/", "/swagger-resources/configuration/ui", - "/swagger-resources/configuration/security", "/webjars/", "/v2/api-docs", - "/v3/api-docs", "/v3/api-docs/swagger-config", - "/api/account/login", "/api/account/preLogin", - "/api/designer/check" - ); - - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) throws ServletException, IOException { - - String requestURI = request.getRequestURI(); - - if (isIgnoredPath(requestURI)) { - filterChain.doFilter(request, response); - return; - } - - String jwtToken = request.getHeader(securityProperties.getJwtTokenHeader()); - if (StrUtil.isBlank(jwtToken)) { - writeUnauthorized(response, "请传入token!"); - return; - } - - try { - // 1. JWT 签名验证 - if (!jwtHelper.validateToken(jwtToken)) { - writeUnauthorized(response, "Token无效!"); - return; - } - - // 2. 解析用户信息 - AuthPrincipalVo principal = jwtHelper.parserToUser(jwtToken); - if (principal == null || principal.getId() == null) { - writeUnauthorized(response, "Token解析失败!"); - return; - } - - // 3. 本地缓存比对 - String cacheToken = SellerLocalCacheUtils.getTokenCache(principal.getId()); - if (StrUtil.isNotBlank(cacheToken)) { - // 本地缓存有,直接比对 - if (!cacheToken.equals(jwtToken)) { - writeUnauthorized(response, "Token已被顶替,请重新登录!"); - return; - } - } else { - // 本地缓存没有,查 Redis - String redisToken = redisUtil.getLoginToken(principal.getId()); - if (!StrUtil.isNotBlank(redisToken)) { - // Redis 也没有,说明真的失效了 - writeUnauthorized(response, "Token已过期,请重新登录!"); - return; - } - if (!redisToken.equals(jwtToken)) { - writeUnauthorized(response, "Token已被顶替,请重新登录!"); - return; - } - // Redis 有, 回填到本地缓存,减少后续 Redis 访问 - SellerLocalCacheUtils.setTokenCache(principal.getId(), jwtToken); - } - - // 4. 设置用户上下文 - UserContext.delete(); - UserContext.setUserHolder(principal); - filterChain.doFilter(request, response); - - } catch (Exception e) { - log.error("JWT验证失败: {}", e.getMessage()); - writeUnauthorized(response, "Token已过期,请重新登录!"); - } - } - - private boolean isIgnoredPath(String requestURI) { - // 检查配置文件中的白名单 - if (securityProperties.getIgnorePaths() != null) { - for (String path : securityProperties.getIgnorePaths()) { - String pattern = path.replace("/**", "").replace("*", ""); - if (requestURI.contains(pattern)) { - return true; - } - } - } - // 检查硬编码的白名单 - for (String url : FILTER_URL) { - if (requestURI.contains(url)) { - return true; - } - } - return false; - } - - private void writeUnauthorized(HttpServletResponse response, String message) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"code\":401,\"message\":\"" + message + "\"}"); - } -} diff --git a/src/main/java/com/aida/seller/common/security/jwt/SellerJwtHelper.java b/src/main/java/com/aida/seller/common/security/jwt/SellerJwtHelper.java deleted file mode 100644 index 30254ba..0000000 --- a/src/main/java/com/aida/seller/common/security/jwt/SellerJwtHelper.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.aida.seller.common.security.jwt; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.DigestUtil; -import com.aida.seller.common.security.config.SecurityProperties; -import com.aida.seller.model.vo.AuthPrincipalVo; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; - -@Slf4j -@Component -@RequiredArgsConstructor -public class SellerJwtHelper { - - private final SecurityProperties securityProperties; - private final ObjectMapper objectMapper; - - private static final String ISSUER = "DWJ"; - - public boolean validateToken(String token) { - try { - Claims claims = parser(token); - return claims != null && StrUtil.isNotEmpty(claims.getSubject()); - } catch (Exception e) { - log.error("JWT签名验证失败: {}", e.getMessage()); - return false; - } - } - - public AuthPrincipalVo parserToUser(String token) { - try { - String subject = parser(token).getSubject(); - if (StrUtil.isNotEmpty(subject)) { - return objectMapper.readValue(subject, AuthPrincipalVo.class); - } - } catch (Exception e) { - log.error("JWT解析用户信息失败: {}", e.getMessage()); - } - return null; - } - - public Claims parser(String token) { - token = token.replaceAll(securityProperties.getJwtTokenPrefix(), ""); - SecretKey key = buildSigningKey(); - return Jwts.parser() - .verifyWith(key) - .build() - .parseSignedClaims(token) - .getPayload(); - } - - private SecretKey buildSigningKey() { - byte[] raw = securityProperties.getJwtSecret().getBytes(StandardCharsets.UTF_8); - if (raw.length < 32) { - raw = DigestUtil.sha256(raw); - } - return Keys.hmacShaKeyFor(raw); - } -} diff --git a/src/main/java/com/aida/seller/common/security/utils/SellerLocalCacheUtils.java b/src/main/java/com/aida/seller/common/security/utils/SellerLocalCacheUtils.java deleted file mode 100644 index 786767b..0000000 --- a/src/main/java/com/aida/seller/common/security/utils/SellerLocalCacheUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.aida.seller.common.security.utils; - -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -@Slf4j -public class SellerLocalCacheUtils { - - private static final ConcurrentHashMap TOKEN_CACHE = new ConcurrentHashMap<>(); - - private static final long EXPIRE_HOURS = 24 * 7 - 1; - - private static class CacheEntry { - private final String token; - private final long expireTime; - - CacheEntry(String token, long expireTime) { - this.token = token; - this.expireTime = expireTime; - } - - String getToken() { - return token; - } - - boolean isExpired() { - return System.currentTimeMillis() > expireTime; - } - } - - public static void setTokenCache(Long userId, String token) { - long expireTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(EXPIRE_HOURS); - TOKEN_CACHE.put(userId, new CacheEntry(token, expireTime)); - } - - public static String getTokenCache(Long userId) { - CacheEntry entry = TOKEN_CACHE.get(userId); - if (entry == null) { - return null; - } - if (entry.isExpired()) { - TOKEN_CACHE.remove(userId); - return null; - } - return entry.getToken(); - } - - public static void delTokenCache(Long userId) { - TOKEN_CACHE.remove(userId); - } - - public static void clearAll() { - TOKEN_CACHE.clear(); - } -} diff --git a/src/main/java/com/aida/seller/common/security/utils/SellerRedisUtil.java b/src/main/java/com/aida/seller/common/security/utils/SellerRedisUtil.java deleted file mode 100644 index e153285..0000000 --- a/src/main/java/com/aida/seller/common/security/utils/SellerRedisUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.aida.seller.common.security.utils; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - -import java.util.concurrent.TimeUnit; - -@Slf4j -@Component -@RequiredArgsConstructor -public class SellerRedisUtil { - - private final RedisTemplate redisTemplate; - - public static final String LOGIN_TOKEN_KEY = "login:token:"; - - public String getLoginToken(Long userId) { - try { - Object value = redisTemplate.opsForValue().get(LOGIN_TOKEN_KEY + userId); - return value != null ? value.toString() : null; - } catch (Exception e) { - log.error("Redis getLoginToken error, userId: {}, error: {}", userId, e.getMessage()); - return null; - } - } - - public void setLoginToken(Long userId, String token, long expireMillis) { - try { - long expireSeconds = expireMillis / 1000; - if (expireSeconds <= 0) { - expireSeconds = 1; - } - redisTemplate.opsForValue().set(LOGIN_TOKEN_KEY + userId, token, expireSeconds, TimeUnit.SECONDS); - } catch (Exception e) { - log.error("Redis setLoginToken error, userId: {}, error: {}", userId, e.getMessage()); - } - } -} diff --git a/src/main/java/com/aida/seller/config/FilterConfig.java b/src/main/java/com/aida/seller/config/FilterConfig.java deleted file mode 100644 index 86177d6..0000000 --- a/src/main/java/com/aida/seller/config/FilterConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.aida.seller.config; - -import com.aida.seller.common.security.filter.SellerAuthenticationFilter; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class FilterConfig { - - @Bean - public FilterRegistrationBean authFilterRegistration( - SellerAuthenticationFilter sellerAuthenticationFilter) { - FilterRegistrationBean registration = new FilterRegistrationBean<>(); - registration.setFilter(sellerAuthenticationFilter); - registration.addUrlPatterns("/*"); - registration.setOrder(1); - return registration; - } -} diff --git a/src/main/java/com/aida/seller/config/WebConfig.java b/src/main/java/com/aida/seller/config/WebConfig.java index 71b685a..71cb725 100644 --- a/src/main/java/com/aida/seller/config/WebConfig.java +++ b/src/main/java/com/aida/seller/config/WebConfig.java @@ -1,13 +1,21 @@ package com.aida.seller.config; +import com.aida.seller.common.interceptor.UserContextInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import jakarta.annotation.Resource; @Configuration -public class WebConfig { +public class WebConfig implements WebMvcConfigurer { + + @Resource + private UserContextInterceptor userContextInterceptor; @Bean public CorsFilter corsFilter() { @@ -22,4 +30,10 @@ public class WebConfig { source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(userContextInterceptor) + .addPathPatterns("/**"); + } } diff --git a/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java b/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java index 77ccf4e..ad8780e 100644 --- a/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java +++ b/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java @@ -61,12 +61,4 @@ public class DesignerController { designerService.audit(dto); return Response.success(); } - - @Operation(summary = "清理用户登录缓存", description = "供 aida-back 登出时远程调用,清除 seller 本地 token 缓存") - @PostMapping("/cache/clear") - public Response clearCache( - @Parameter(description = "用户ID") @RequestParam Long userId) { - com.aida.seller.common.security.utils.SellerLocalCacheUtils.delTokenCache(userId); - return Response.success(); - } } diff --git a/src/main/java/com/aida/seller/util/JwtUtil.java b/src/main/java/com/aida/seller/util/JwtUtil.java deleted file mode 100644 index ff5eee4..0000000 --- a/src/main/java/com/aida/seller/util/JwtUtil.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.aida.seller.util; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import com.aida.seller.config.JwtConfig; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@Component -@RequiredArgsConstructor -public class JwtUtil { - - private final JwtConfig jwtConfig; - - private SecretKey getSecretKey() { - return Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes(StandardCharsets.UTF_8)); - } - - public String generateToken(Long userId, String username) { - Map claims = new HashMap<>(); - claims.put("userId", userId); - claims.put("username", username); - return createToken(claims, username); - } - - public String generateToken(Long userId, String username, Map additionalClaims) { - Map claims = new HashMap<>(); - claims.put("userId", userId); - claims.put("username", username); - if (additionalClaims != null) { - claims.putAll(additionalClaims); - } - return createToken(claims, username); - } - - private String createToken(Map claims, String subject) { - return Jwts.builder() - .claims(claims) - .subject(subject) - .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration())) - .signWith(getSecretKey()) - .compact(); - } - - public Claims parseToken(String token) { - return Jwts.parser() - .verifyWith(getSecretKey()) - .build() - .parseSignedClaims(token) - .getPayload(); - } - - public String getUsernameFromToken(String token) { - Claims claims = parseToken(token); - return claims.getSubject(); - } - - public Long getUserIdFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("userId", Long.class); - } - - public boolean isTokenExpired(String token) { - try { - Claims claims = parseToken(token); - return claims.getExpiration().before(new Date()); - } catch (ExpiredJwtException e) { - return true; - } - } - - public boolean validateToken(String token) { - if (StrUtil.isBlank(token)) { - return false; - } - try { - parseToken(token); - return true; - } catch (JwtException e) { - return false; - } - } - - public String refreshToken(String token) { - Claims claims = parseToken(token); - String username = claims.getSubject(); - Long userId = claims.get("userId", Long.class); - return generateToken(userId, username); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9f8d5ef..f469499 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,9 @@ +# ============================================================ +# aida-seller - 本地配置(不区分环境) +# 公共配置(DB、Redis、RabbitMQ、MinIO、API Keys 等)由 Nacos 统一管理 +# 鉴权逻辑已全部迁移至 Gateway(GlobalAuthWebFilter) +# ============================================================ + server: port: 5568 servlet: @@ -6,43 +12,8 @@ server: spring: application: name: aida-seller - profiles: - active: dev - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:aida_seller}?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: ${DB_USER:root} - password: ${DB_PASSWORD:root} - servlet: - multipart: - enabled: true - max-file-size: 10MB - max-request-size: 10MB - data: - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:} - database: 0 - security: - jwt-secret: ${BACK_JWT_SECRET:JWTSECRET} - jwt-token-header: Authorization - jwt-token-prefix: Bearer- - jwt-expiration: ${BACK_JWT_EXPIRATION:8640000000} - ignore-paths: - - /favicon.ico - - /doc.html - - /swagger-ui.html - - /swagger-ui/** - - /swagger-resources/** - - /v2/api-docs - - /v3/api-docs/** - - /webjars/** - - /api/account/login - - /api/account/preLogin - - /api/designer/check - - /api/global-award/contestants/export +# ---------- MyBatis-Plus(seller 私有:id 自增,logic-delete 字段不同) ---------- mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.aida.seller.module.*.entity @@ -56,27 +27,13 @@ mybatis-plus: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl -# MinIO 配置 +# ---------- MinIO(seller 私有:单个 bucket) ---------- minio: - endpoint: https://www.minio-api.aida.com.hk - access-key: admin - secret-key: Aidlab123123! + endpoint: ${MINIO_ENDPOINT:https://www.minio-api.aida.com.hk} + access-key: ${MINIO_ACCESS_KEY:admin} + secret-key: ${MINIO_SECRET_KEY:Aidlab123123!} default-bucket: aida-user -# JWT 配置 -jwt: - secret: ${JWT_SECRET:YourSuperSecretKeyForJWTTokenGenerationMustBeAtLeast256Bits} - expiration: ${JWT_EXPIRATION:86400000} - -# Knife4j 配置 -knife4j: - enable: true - setting: - language: zh_cn - -# 日志配置 logging: level: com.aida: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n" diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml index 7a42ffa..70f80ec 100644 --- a/src/main/resources/bootstrap.yml +++ b/src/main/resources/bootstrap.yml @@ -1,10 +1,20 @@ +# ============================================================ +# aida-seller - Bootstrap +# 通过 NACOS_NAMESPACE 环境变量切换命名空间(dev / test / prod) +# 示例:docker run -e NACOS_NAMESPACE=prod ... +# ============================================================ + spring: application: name: aida-seller config: - import: "optional:nacos:${spring.application.name}.yml" + import: optional:nacos:aida-public-${NACOS_NAMESPACE:test}.yml cloud: nacos: discovery: - server-addr: 127.0.0.1:8848 - namespace: dev + server-addr: ${NACOS_HOST:127.0.0.1:8848} + namespace: ${NACOS_NAMESPACE:test} + config: + server-addr: ${NACOS_HOST:127.0.0.1:8848} + namespace: ${NACOS_NAMESPACE:test} + file-extension: yaml