网关问题

This commit is contained in:
2025-12-02 18:46:03 +08:00
parent 90ddcf7af3
commit 9cb4844be4
13 changed files with 1226 additions and 5 deletions

View File

@@ -0,0 +1,82 @@
package org.xyzh.common.auth.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.xyzh.common.auth.contants.AuthContants;
import java.io.IOException;
import java.util.Collections;
/**
* @description Gateway 认证模式配置 - 信任 Gateway 传递的用户信息
* @author yslg
* @since 2025-12-02
*
* 当启用 gateway-mode 时,微服务不再独立验证 JWT而是信任 Gateway 传递的用户信息
* 配置项auth.gateway-mode=true
*/
@Configuration
@ConditionalOnProperty(name = "auth.gateway-mode", havingValue = "true")
public class GatewayAuthConfig {
private static final Logger log = LoggerFactory.getLogger(GatewayAuthConfig.class);
/**
* Gateway 信任过滤器 - 从请求头获取用户信息
* 替代 JwtAuthenticationFilter
*/
@Bean
public GatewayTrustFilter gatewayTrustFilter() {
log.info("启用 Gateway 认证模式,微服务将信任 Gateway 传递的用户信息");
return new GatewayTrustFilter();
}
/**
* Gateway 信任过滤器实现
*/
public static class GatewayTrustFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(GatewayTrustFilter.class);
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
// 从 Gateway 传递的请求头获取用户信息
String userId = request.getHeader(AuthContants.USER_ID_ATTRIBUTE);
String username = request.getHeader(AuthContants.USERNAME_ATTRIBUTE);
if (StringUtils.hasText(userId)) {
// 构造简化的 Principal使用 userId 作为身份标识)
// 注意:完整的 LoginDomain 应该从 Redis 加载,这里只是基本身份验证
// 设置到 Spring Security 上下文
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(authentication);
// 同时将用户信息设置到 request attributes 中,供业务代码使用
request.setAttribute(AuthContants.USER_ID_ATTRIBUTE, userId);
if (StringUtils.hasText(username)) {
request.setAttribute(AuthContants.USERNAME_ATTRIBUTE, username);
}
log.debug("从 Gateway 获取用户信息: userId={}, username={}", userId, username);
}
filterChain.doFilter(request, response);
}
}
}

View File

@@ -1,5 +1,7 @@
package org.xyzh.common.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -15,17 +17,29 @@ import org.xyzh.redis.service.RedisService;
@EnableMethodSecurity
public class SecurityConfig {
/**
* JWT 认证过滤器 - 仅在非 Gateway 模式下启用
* 当 auth.gateway-mode=false 或未配置时使用
*/
@Bean
@ConditionalOnProperty(name = "auth.gateway-mode", havingValue = "false", matchIfMissing = true)
public JwtAuthenticationFilter jwtAuthenticationFilter(TokenParser tokenParser,
AuthProperties authProperties,
RedisService redisService) {
return new JwtAuthenticationFilter(tokenParser, authProperties, redisService);
}
/**
* Security 过滤器链配置
* 根据是否启用 Gateway 模式决定是否添加 JWT 过滤器
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
AuthProperties authProperties,
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
AuthProperties authProperties,
@Autowired(required = false) JwtAuthenticationFilter jwtAuthenticationFilter,
@Autowired(required = false) GatewayAuthConfig.GatewayTrustFilter gatewayTrustFilter) throws Exception {
http
.csrf(csrf -> csrf.disable())
.formLogin(form -> form.disable())
@@ -45,8 +59,16 @@ public class SecurityConfig {
authz
.requestMatchers("/error", "/favicon.ico").permitAll()
.anyRequest().authenticated();
})
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
});
// 根据模式添加对应的过滤器
if (jwtAuthenticationFilter != null) {
// 非 Gateway 模式:使用 JWT 过滤器
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
} else if (gatewayTrustFilter != null) {
// Gateway 模式:使用信任过滤器
http.addFilterBefore(gatewayTrustFilter, UsernamePasswordAuthenticationFilter.class);
}
return http.build();
}

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>urban-lifeline</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh</groupId>
<artifactId>gateway</artifactId>
<version>${urban-lifeline.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 认证模块JWT验证 -->
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
</dependency>
<!-- Redis用于Token验证、限流等 -->
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-redis</artifactId>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
</dependency>
<!-- 核心模块 -->
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
</dependency>
<!-- Spring Boot Actuator健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Log4j2 日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- WebFluxGateway依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 配置处理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- FastJson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.xyzh.gateway.GatewayApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package org.xyzh.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @description Gateway 网关启动类
* @author yslg
* @since 2025-12-02
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
System.out.println("========================================");
System.out.println(" Gateway 网关服务启动成功!");
System.out.println("========================================");
}
}

View File

@@ -0,0 +1,78 @@
package org.xyzh.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description Gateway 动态路由配置Java 代码方式)
* @author yslg
* @since 2025-12-02
*
* 这是路由配置的第二种方式,与 application.yml 中的配置二选一或配合使用
* 优点:灵活性更高,可以动态添加/修改路由,支持更复杂的路由逻辑
*/
@Configuration
public class GatewayRouteConfig {
/**
* 通过代码方式配置路由(示例,可根据需要启用)
*/
// @Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 认证服务路由
.route("auth-service", r -> r
.path("/auth/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway", "gateway-service")
)
.uri("lb://auth-service")
)
// 系统服务路由
.route("system-service", r -> r
.path("/system/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway", "gateway-service")
)
.uri("lb://system-service")
)
// 日志服务路由
.route("log-service", r -> r
.path("/log/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway", "gateway-service")
)
.uri("lb://log-service")
)
// 文件服务路由
.route("file-service", r -> r
.path("/file/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway", "gateway-service")
)
.uri("lb://file-service")
)
// WebSocket 路由示例
.route("websocket-route", r -> r
.path("/ws/**")
.uri("lb:ws://system-service")
)
.build();
}
/**
* 动态路由刷新端点(如需要)
* 可以配合 Nacos 配置中心实现动态路由更新
*/
}

View File

@@ -0,0 +1,47 @@
package org.xyzh.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* @description 限流配置 - 基于 Redis 的令牌桶算法
* @author yslg
* @since 2025-12-02
*/
@Configuration
public class RateLimiterConfig {
/**
* 基于 IP 地址的限流
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress() != null
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
: "unknown";
return Mono.just(ip);
};
}
/**
* 基于用户 ID 的限流(从请求头获取)
*/
// @Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
/**
* 基于 API 路径的限流
*/
// @Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}

View File

@@ -0,0 +1,177 @@
package org.xyzh.gateway.filter;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.xyzh.common.auth.config.AuthProperties;
import org.xyzh.common.auth.contants.AuthContants;
import org.xyzh.common.auth.token.TokenParser;
import org.xyzh.common.core.domain.ResultDomain;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @description Gateway 全局认证过滤器 - 用于验证 JWT Token
* @author yslg
* @since 2025-12-02
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);
@Autowired
private TokenParser tokenParser;
@Autowired
private AuthProperties authProperties;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
log.debug("Gateway 请求路径: {}", path);
// 1. 检查认证功能是否启用
if (!authProperties.isEnabled()) {
log.debug("认证功能未启用,直接放行");
return chain.filter(exchange);
}
// 2. 检查是否在白名单中
if (isWhitelisted(path)) {
log.debug("路径在白名单中,跳过认证: {}", path);
return chain.filter(exchange);
}
// 3. 提取 Token
String token = extractToken(request);
if (!StringUtils.hasText(token)) {
log.warn("请求缺少 Token: {}", path);
return unauthorizedResponse(exchange, "未提供认证令牌,请先登录");
}
try {
// 4. 验证 Token
if (tokenParser.isTokenExpired(token)) {
log.warn("Token 已过期: {}", path);
return unauthorizedResponse(exchange, "认证令牌已过期,请重新登录");
}
// 5. 获取用户信息
String userId = tokenParser.getUserIdFromToken(token);
if (!StringUtils.hasText(userId)) {
log.warn("Token 中未找到用户ID: {}", path);
return unauthorizedResponse(exchange, "认证令牌无效");
}
// 6. 验证 Token 有效性
if (!tokenParser.validateToken(token, userId)) {
log.warn("Token 验证失败: userId={}, path={}", userId, path);
return unauthorizedResponse(exchange, "认证令牌验证失败");
}
// 7. 将用户信息添加到请求头中,传递给下游服务
ServerHttpRequest mutatedRequest = request.mutate()
.header(AuthContants.USER_ID_ATTRIBUTE, userId)
.header(AuthContants.TOKEN_ATTRIBUTE, token)
.build();
log.debug("Token 验证成功: userId={}, path={}", userId, path);
// 8. 继续执行过滤器链
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
log.error("Token 解析或验证异常: path={}, error={}", path, e.getMessage(), e);
return unauthorizedResponse(exchange, "认证令牌解析失败: " + e.getMessage());
}
}
/**
* 检查路径是否在白名单中
*/
private boolean isWhitelisted(String path) {
// 1. 检查认证相关接口
if (authProperties.getAuthPaths() != null) {
for (String pattern : authProperties.getAuthPaths()) {
if (pattern != null && pathMatcher.match(pattern, path)) {
return true;
}
}
}
// 2. 检查通用白名单
if (authProperties.getWhitelist() != null) {
for (String pattern : authProperties.getWhitelist()) {
if (pattern != null && pathMatcher.match(pattern, path)) {
return true;
}
}
}
return false;
}
/**
* 从请求头中提取 Token
*/
private String extractToken(ServerHttpRequest request) {
List<String> headers = request.getHeaders().get(authProperties.getTokenHeader());
if (headers == null || headers.isEmpty()) {
return null;
}
String header = headers.get(0);
if (!StringUtils.hasText(header)) {
return null;
}
// 支持 Bearer 前缀
String prefix = authProperties.getTokenPrefix();
if (StringUtils.hasText(prefix) && header.startsWith(prefix)) {
return header.substring(prefix.length()).trim();
}
// 也支持直接传递 Token不带前缀
return header.trim();
}
/**
* 返回未授权响应
*/
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
ResultDomain<Object> result = ResultDomain.failure(HttpStatus.UNAUTHORIZED.value(), message);
String json = JSON.toJSONString(result);
DataBuffer buffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
// 优先级设置为最高,确保在其他过滤器之前执行
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,54 @@
package org.xyzh.gateway.filter;
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.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @description Gateway 全局日志过滤器 - 记录请求信息
* @author yslg
* @since 2025-12-02
*/
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(LogGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
long startTime = System.currentTimeMillis();
String path = request.getURI().getPath();
String method = request.getMethod().name();
String remoteAddress = request.getRemoteAddress() != null
? request.getRemoteAddress().getAddress().getHostAddress()
: "unknown";
log.info("==> Gateway 请求: {} {} | 来源: {}", method, path, remoteAddress);
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
int statusCode = exchange.getResponse().getStatusCode() != null
? exchange.getResponse().getStatusCode().value()
: 0;
log.info("<== Gateway 响应: {} {} | 状态: {} | 耗时: {}ms",
method, path, statusCode, duration);
})
);
}
@Override
public int getOrder() {
// 优先级设置较低,在认证过滤器之后执行
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,70 @@
package org.xyzh.gateway.handler;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.xyzh.common.core.domain.ResultDomain;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* @description Gateway 全局异常处理器
* @author yslg
* @since 2025-12-02
*/
@Component
@Order(-1) // 优先级设置为最高
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
ResultDomain<Object> result;
HttpStatus httpStatus;
// 根据不同异常类型返回不同的错误信息
if (ex instanceof NotFoundException) {
log.error("服务未找到: {}", ex.getMessage());
httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
result = ResultDomain.failure(httpStatus.value(), "服务暂时不可用: " + ex.getMessage());
} else if (ex instanceof ResponseStatusException rse) {
log.error("响应状态异常: {}", rse.getMessage());
httpStatus = (HttpStatus) rse.getStatusCode();
result = ResultDomain.failure(httpStatus.value(), rse.getReason());
} else if (ex instanceof IllegalStateException) {
log.error("非法状态异常: {}", ex.getMessage());
httpStatus = HttpStatus.BAD_REQUEST;
result = ResultDomain.failure(httpStatus.value(), "请求参数错误: " + ex.getMessage());
} else {
log.error("Gateway 内部错误", ex);
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
result = ResultDomain.failure(httpStatus.value(), "网关内部错误,请稍后重试");
}
response.setStatusCode(httpStatus);
String json = JSON.toJSONString(result);
DataBuffer buffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}

View File

@@ -0,0 +1,19 @@
spring:
cloud:
gateway:
routes:
# 开发环境可以添加更详细的路由配置或测试路由
# Nacos 管理界面路由(开发专用)
- id: nacos-console
uri: http://${NACOS_SERVER_ADDR:localhost:8848}
predicates:
- Path=/nacos/**
# 开发环境日志
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.web.reactive: DEBUG
reactor.netty: DEBUG
org.xyzh: DEBUG

View File

@@ -0,0 +1,133 @@
server:
port: 8080
spring:
application:
name: gateway-service
# 配置中心
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
namespace: dev
group: DEFAULT_GROUP
config:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
file-extension: yml
namespace: dev
group: DEFAULT_GROUP
# Gateway 路由配置
gateway:
# 服务发现路由(自动路由)
discovery:
locator:
enabled: false # 关闭自动路由,使用手动配置
# 手动配置路由
routes:
# ==================== 认证服务路由 ====================
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
# ==================== 系统服务路由 ====================
- id: system-service
uri: lb://system-service
predicates:
- Path=/system/**
filters:
- StripPrefix=1
# ==================== 日志服务路由 ====================
- id: log-service
uri: lb://log-service
predicates:
- Path=/log/**
filters:
- StripPrefix=1
# ==================== 文件服务路由 ====================
- id: file-service
uri: lb://file-service
predicates:
- Path=/file/**
filters:
- StripPrefix=1
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
# Redis 配置(用于限流、缓存)
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 20
max-wait: -1ms
max-idle: 10
min-idle: 5
# 认证配置
auth:
enabled: true
token-header: Authorization
token-prefix: "Bearer "
# 认证接口白名单login/logout/captcha/refresh
auth-paths:
- /auth/login
- /auth/logout
- /auth/captcha
- /auth/refresh
# 通用白名单Swagger、健康检查等
whitelist:
- /actuator/**
- /v3/api-docs/**
- /swagger-ui/**
- /swagger-resources/**
- /webjars/**
- /doc.html
- /favicon.ico
- /error
# Actuator 监控端点
management:
endpoints:
web:
exposure:
include: health,info,gateway
endpoint:
health:
show-details: always
# 日志配置
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.xyzh.gateway: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"

View File

@@ -10,6 +10,7 @@
<modules>
<module>common</module>
<module>apis</module>
<module>gateway</module>
<module>log</module>
<module>system</module>
<module>auth</module>