diff --git a/pom.xml b/pom.xml index fc31d89..a99708b 100644 --- a/pom.xml +++ b/pom.xml @@ -25,14 +25,14 @@ UTF-8 3.5.7 - 8.5.7 + 8.0.3 0.12.3 - 5.8.26 + 5.8.23 3.13.0 - 4.5.0 + 4.4.0 - 2023.0.1.0 - 2023.0.0 + 2023.0.3.4 + 2023.0.4 @@ -92,7 +92,6 @@ com.mysql mysql-connector-j - 8.2.0 diff --git a/src/main/java/com/aida/seller/common/context/UserContext.java b/src/main/java/com/aida/seller/common/context/UserContext.java index 40f9043..2290cd8 100644 --- a/src/main/java/com/aida/seller/common/context/UserContext.java +++ b/src/main/java/com/aida/seller/common/context/UserContext.java @@ -1,24 +1,55 @@ package com.aida.seller.common.context; +import com.aida.seller.common.exception.UnauthorizedException; import com.aida.seller.model.vo.AuthPrincipalVo; public class UserContext { private static final ThreadLocal userHolder = new ThreadLocal<>(); - - public static AuthPrincipalVo getUserHolder() { - return userHolder.get(); - } - - public static void delete() { - userHolder.remove(); - } + private static final ThreadLocal optionalAuth = ThreadLocal.withInitial(() -> false); public static void setUserHolder(AuthPrincipalVo authPrincipalVo) { userHolder.set(authPrincipalVo); } - public static Long getUserId() { + public static void setOptionalAuth(boolean value) { + optionalAuth.set(value); + } + + public static AuthPrincipalVo getUserHolder() { AuthPrincipalVo holder = userHolder.get(); - return holder != null ? holder.getId() : null; + if (holder == null) { + if (optionalAuth.get()) { + return null; + } + throw new UnauthorizedException("Gateway token verification failed"); + } + if (!"AIDA".equals(holder.getSource())) { + throw new UnauthorizedException("Gateway token verification failed"); + } + return holder; + } + + public static void delete() { + userHolder.remove(); + optionalAuth.remove(); + } + + public static Long getUserId() { + return getUserHolder() == null ? null : getUserHolder().getId(); + } + + //买家端请求需要调用此方法获取买家id + public static Long getBuyerId() { + AuthPrincipalVo holder = userHolder.get(); + if (holder == null) { + if (optionalAuth.get()) { + return null; + } + throw new UnauthorizedException("Gateway token verification failed"); + } + if (!"BUYER".equals(holder.getSource())) { + throw new UnauthorizedException("Gateway token verification failed"); + } + return holder.getId(); } } diff --git a/src/main/java/com/aida/seller/common/exception/GlobalExceptionHandler.java b/src/main/java/com/aida/seller/common/exception/GlobalExceptionHandler.java index 70deaa9..693d1cb 100644 --- a/src/main/java/com/aida/seller/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/aida/seller/common/exception/GlobalExceptionHandler.java @@ -18,6 +18,15 @@ import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorizedException(UnauthorizedException e) { + log.error("Unauthorized: {}", e.getMessage()); + return new ResponseEntity<>( + Response.fail(401, e.getMessage()), + HttpStatus.UNAUTHORIZED + ); + } + @ExceptionHandler(BusinessException.class) public Response> handleBusinessException(BusinessException e) { log.error("业务异常: code={}, msg={}", e.getCode(), e.getMsg()); diff --git a/src/main/java/com/aida/seller/common/exception/UnauthorizedException.java b/src/main/java/com/aida/seller/common/exception/UnauthorizedException.java new file mode 100644 index 0000000..512e1c8 --- /dev/null +++ b/src/main/java/com/aida/seller/common/exception/UnauthorizedException.java @@ -0,0 +1,15 @@ +package com.aida.seller.common.exception; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class UnauthorizedException extends RuntimeException { + + public UnauthorizedException(String message) { + super(message); + } + + public UnauthorizedException() { + super("Gateway token verification failed"); + } +} diff --git a/src/main/java/com/aida/seller/common/interceptor/UserContextInterceptor.java b/src/main/java/com/aida/seller/common/interceptor/UserContextInterceptor.java index a6d4621..68eef0c 100644 --- a/src/main/java/com/aida/seller/common/interceptor/UserContextInterceptor.java +++ b/src/main/java/com/aida/seller/common/interceptor/UserContextInterceptor.java @@ -3,13 +3,18 @@ package com.aida.seller.common.interceptor; import com.aida.seller.common.context.UserContext; import com.aida.seller.model.vo.AuthPrincipalVo; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.List; +import java.util.stream.Collectors; + /** * 从 Gateway 转发的请求头中读取已鉴权的用户身份,写入 UserContext。 * @@ -18,13 +23,40 @@ import org.springframework.web.servlet.HandlerInterceptor; */ @Slf4j @Component -@RequiredArgsConstructor public class UserContextInterceptor implements HandlerInterceptor { private static final String USER_ID_HEADER = "X-User-Id"; private static final String USER_INFO_HEADER = "X-User-Info"; - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + @Value("${gateway.auth.optional-auth-paths:}") + private List optionalAuthPaths; + + private List localOptionalAuthPaths; + + @PostConstruct + public void init() { + localOptionalAuthPaths = optionalAuthPaths.stream() + .map(this::toLocalPath) + .collect(Collectors.toList()); + log.info("Local optional auth paths: {}", localOptionalAuthPaths); + } + + /** + * 将 Gateway 前端路径(如 /seller/listing/shop)转换为服务本地路径(如 /listing/shop)。 + */ + private String toLocalPath(String gatewayPath) { + if (gatewayPath == null) { + return gatewayPath; + } + if (gatewayPath.startsWith("/seller")) { + String local = gatewayPath.substring("/seller".length()); + return local.isEmpty() ? "/" : local; + } + return gatewayPath; + } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -36,10 +68,24 @@ public class UserContextInterceptor implements HandlerInterceptor { } catch (Exception e) { log.warn("Failed to parse X-User-Info header: {}", e.getMessage()); } + } else if (isOptionalAuthPath(request.getRequestURI())) { + UserContext.setOptionalAuth(true); } return true; } + private boolean isOptionalAuthPath(String requestUri) { + if (localOptionalAuthPaths == null || localOptionalAuthPaths.isEmpty()) { + return false; + } + for (String pattern : localOptionalAuthPaths) { + if (pathMatcher.match(pattern, requestUri)) { + return true; + } + } + return false; + } + @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { diff --git a/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java index 9d7d1cc..672bcec 100644 --- a/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java +++ b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java @@ -318,7 +318,7 @@ public class ListingServiceImpl extends ServiceImpl page = this.page(pageParam, queryWrapper); Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); - boolean loggedIn = UserContext.getUserId() != null; + boolean loggedIn = UserContext.getBuyerId() != null; result.setRecords(page.getRecords().stream().map(entity -> { ListingPageVO vo = new ListingPageVO(); BeanUtils.copyProperties(entity, vo);
@@ -18,13 +23,40 @@ import org.springframework.web.servlet.HandlerInterceptor; */ @Slf4j @Component -@RequiredArgsConstructor public class UserContextInterceptor implements HandlerInterceptor { private static final String USER_ID_HEADER = "X-User-Id"; private static final String USER_INFO_HEADER = "X-User-Info"; - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + @Value("${gateway.auth.optional-auth-paths:}") + private List optionalAuthPaths; + + private List localOptionalAuthPaths; + + @PostConstruct + public void init() { + localOptionalAuthPaths = optionalAuthPaths.stream() + .map(this::toLocalPath) + .collect(Collectors.toList()); + log.info("Local optional auth paths: {}", localOptionalAuthPaths); + } + + /** + * 将 Gateway 前端路径(如 /seller/listing/shop)转换为服务本地路径(如 /listing/shop)。 + */ + private String toLocalPath(String gatewayPath) { + if (gatewayPath == null) { + return gatewayPath; + } + if (gatewayPath.startsWith("/seller")) { + String local = gatewayPath.substring("/seller".length()); + return local.isEmpty() ? "/" : local; + } + return gatewayPath; + } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -36,10 +68,24 @@ public class UserContextInterceptor implements HandlerInterceptor { } catch (Exception e) { log.warn("Failed to parse X-User-Info header: {}", e.getMessage()); } + } else if (isOptionalAuthPath(request.getRequestURI())) { + UserContext.setOptionalAuth(true); } return true; } + private boolean isOptionalAuthPath(String requestUri) { + if (localOptionalAuthPaths == null || localOptionalAuthPaths.isEmpty()) { + return false; + } + for (String pattern : localOptionalAuthPaths) { + if (pathMatcher.match(pattern, requestUri)) { + return true; + } + } + return false; + } + @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { diff --git a/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java index 9d7d1cc..672bcec 100644 --- a/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java +++ b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java @@ -318,7 +318,7 @@ public class ListingServiceImpl extends ServiceImpl page = this.page(pageParam, queryWrapper); Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); - boolean loggedIn = UserContext.getUserId() != null; + boolean loggedIn = UserContext.getBuyerId() != null; result.setRecords(page.getRecords().stream().map(entity -> { ListingPageVO vo = new ListingPageVO(); BeanUtils.copyProperties(entity, vo);