网关问题
This commit is contained in:
396
docs/网关认证方案.md
Normal file
396
docs/网关认证方案.md
Normal file
@@ -0,0 +1,396 @@
|
||||
# Gateway 认证方案说明
|
||||
|
||||
## 问题背景
|
||||
|
||||
在微服务架构中,如果同时使用 Gateway 和 common-auth 模块,会出现**重复认证**的问题:
|
||||
|
||||
```
|
||||
浏览器 → Gateway (AuthGlobalFilter 验证 JWT)
|
||||
→ 微服务 (JwtAuthenticationFilter 再次验证 JWT) ❌ 重复!
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
|
||||
提供两种认证模式,通过配置选择:
|
||||
|
||||
### **模式一:Gateway 统一认证(推荐)**
|
||||
|
||||
Gateway 负责认证,微服务信任 Gateway 传递的用户信息。
|
||||
|
||||
#### 架构流程
|
||||
|
||||
```
|
||||
浏览器
|
||||
↓ (带 JWT Token)
|
||||
Nginx (80端口)
|
||||
↓
|
||||
Gateway (8080端口)
|
||||
├─ AuthGlobalFilter: 验证 JWT ✓
|
||||
├─ 提取用户信息 (userId, username)
|
||||
├─ 添加到请求头传递给下游
|
||||
└─ 路由到微服务
|
||||
↓
|
||||
微服务
|
||||
└─ GatewayTrustFilter: 从请求头获取用户信息 ✓
|
||||
```
|
||||
|
||||
#### 配置方式
|
||||
|
||||
**1. Gateway 服务配置** (`gateway/application.yml`)
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
auth-paths:
|
||||
- /auth/login
|
||||
- /auth/logout
|
||||
whitelist:
|
||||
- /actuator/**
|
||||
- /v3/api-docs/**
|
||||
```
|
||||
|
||||
**2. 微服务配置** (`system/application.yml`, `log/application.yml` 等)
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true # 关键:启用 Gateway 模式
|
||||
```
|
||||
|
||||
#### 优点
|
||||
- ✅ 避免重复认证,性能更好
|
||||
- ✅ 统一认证逻辑,易维护
|
||||
- ✅ 微服务之间调用不需要传递 JWT
|
||||
|
||||
---
|
||||
|
||||
### **模式二:微服务独立认证**
|
||||
|
||||
每个微服务独立验证 JWT,Gateway 不做认证。
|
||||
|
||||
#### 架构流程
|
||||
|
||||
```
|
||||
浏览器
|
||||
↓ (带 JWT Token)
|
||||
Nginx
|
||||
↓
|
||||
Gateway
|
||||
└─ 直接路由(不验证)
|
||||
↓
|
||||
微服务
|
||||
└─ JwtAuthenticationFilter: 验证 JWT ✓
|
||||
```
|
||||
|
||||
#### 配置方式
|
||||
|
||||
**1. Gateway 服务配置**
|
||||
```yaml
|
||||
auth:
|
||||
enabled: false # 关键:关闭 Gateway 认证
|
||||
```
|
||||
|
||||
**2. 微服务配置**
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: false # 或不配置(默认 false)
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
```
|
||||
|
||||
#### 适用场景
|
||||
- 微服务需要直接对外暴露(不经过 Gateway)
|
||||
- 对安全性要求极高,需要每层都验证
|
||||
|
||||
---
|
||||
|
||||
## 配置文件对比
|
||||
|
||||
### Gateway 服务 (`gateway/application.yml`)
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: gateway-service
|
||||
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: auth-service
|
||||
uri: lb://auth-service
|
||||
predicates:
|
||||
- Path=/auth/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
- id: system-service
|
||||
uri: lb://system-service
|
||||
predicates:
|
||||
- Path=/system/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
# 认证配置
|
||||
auth:
|
||||
enabled: true # 是否启用认证
|
||||
gateway-mode: false # Gateway 本身不需要此配置
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
auth-paths:
|
||||
- /auth/login
|
||||
- /auth/logout
|
||||
- /auth/captcha
|
||||
- /auth/refresh
|
||||
whitelist:
|
||||
- /actuator/**
|
||||
- /v3/api-docs/**
|
||||
```
|
||||
|
||||
### 微服务配置 (Gateway 模式)
|
||||
|
||||
**auth-service/application.yml**
|
||||
```yaml
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: auth-service
|
||||
|
||||
# 认证配置
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true # 关键:信任 Gateway
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
whitelist:
|
||||
- /v3/api-docs/**
|
||||
- /actuator/**
|
||||
```
|
||||
|
||||
**system-service/application.yml**
|
||||
```yaml
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: system-service
|
||||
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true # 关键:信任 Gateway
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
### Gateway 认证流程
|
||||
|
||||
**AuthGlobalFilter (Gateway 层)**
|
||||
```java
|
||||
1. 检查请求路径是否在白名单
|
||||
2. 提取 Authorization 请求头中的 JWT Token
|
||||
3. 验证 Token 是否过期
|
||||
4. 验证 Token 签名是否有效
|
||||
5. 提取用户信息 (userId, username)
|
||||
6. 将用户信息添加到请求头:
|
||||
- X-User-Id: {userId}
|
||||
- X-Username: {username}
|
||||
7. 路由到下游微服务
|
||||
```
|
||||
|
||||
### 微服务信任流程
|
||||
|
||||
**GatewayTrustFilter (微服务层)**
|
||||
```java
|
||||
1. 从请求头获取 Gateway 传递的用户信息:
|
||||
- X-User-Id
|
||||
- X-Username
|
||||
2. 构造 Spring Security 认证对象
|
||||
3. 设置到 SecurityContext
|
||||
4. 设置到 request attributes(供业务代码使用)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 内网安全
|
||||
|
||||
采用 Gateway 模式时,需确保:
|
||||
|
||||
1. **微服务不对外暴露**
|
||||
- 只能通过 Gateway 访问
|
||||
- 使用 Kubernetes Network Policy 或防火墙隔离
|
||||
|
||||
2. **请求头保护**
|
||||
- Gateway 在转发前清除任何客户端传递的 `X-User-Id` 等头
|
||||
- 防止伪造用户身份
|
||||
|
||||
3. **Gateway 过滤器增强**(可选)
|
||||
```java
|
||||
// 在 Gateway 中清除客户端可能伪造的请求头
|
||||
ServerHttpRequest mutatedRequest = request.mutate()
|
||||
.headers(headers -> {
|
||||
headers.remove("X-User-Id");
|
||||
headers.remove("X-Username");
|
||||
})
|
||||
.header(AuthContants.USER_ID_ATTRIBUTE, userId)
|
||||
.header(AuthContants.TOKEN_ATTRIBUTE, token)
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试 Gateway 模式
|
||||
|
||||
**1. 登录获取 Token**
|
||||
```bash
|
||||
curl -X POST http://localhost/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"123456"}'
|
||||
|
||||
# 响应
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"userId": "1001"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 使用 Token 访问受保护接口**
|
||||
```bash
|
||||
curl -X GET http://localhost/api/system/user/profile \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**3. 查看日志验证单次认证**
|
||||
```
|
||||
Gateway 日志:
|
||||
[Gateway] Token 验证成功: userId=1001, path=/system/user/profile
|
||||
|
||||
System-Service 日志:
|
||||
[GatewayTrustFilter] 从 Gateway 获取用户信息: userId=1001, username=admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 迁移指南
|
||||
|
||||
### 从独立认证迁移到 Gateway 统一认证
|
||||
|
||||
**步骤 1**: 更新所有微服务配置
|
||||
```yaml
|
||||
auth:
|
||||
gateway-mode: true # 添加这一行
|
||||
```
|
||||
|
||||
**步骤 2**: 重启服务(先重启微服务,后重启 Gateway)
|
||||
```bash
|
||||
# 重启微服务
|
||||
docker-compose restart auth-service system-service log-service
|
||||
|
||||
# 重启 Gateway
|
||||
docker-compose restart gateway
|
||||
```
|
||||
|
||||
**步骤 3**: 验证功能
|
||||
- 测试登录
|
||||
- 测试受保护接口访问
|
||||
- 检查日志确认只认证一次
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: Gateway 模式下,微服务之间如何调用?
|
||||
|
||||
**A**: 微服务间调用不需要传递 JWT Token,直接调用即可。Gateway已经验证过身份。
|
||||
|
||||
```java
|
||||
// 微服务 A 调用微服务 B
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public void callServiceB() {
|
||||
// 直接调用,不需要添加 Authorization 头
|
||||
String result = restTemplate.getForObject(
|
||||
"http://service-b/api/xxx",
|
||||
String.class
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Q2: 如何获取当前登录用户信息?
|
||||
|
||||
**A**: 使用 `@HttpLogin` 注解或从 SecurityContext 获取。
|
||||
|
||||
```java
|
||||
// 方式一:使用注解(推荐)
|
||||
@GetMapping("/profile")
|
||||
public ResultDomain<UserDTO> getProfile(@HttpLogin LoginDomain loginDomain) {
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
// ...
|
||||
}
|
||||
|
||||
// 方式二:从 SecurityContext 获取
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
String userId = (String) auth.getPrincipal();
|
||||
```
|
||||
|
||||
### Q3: Gateway 模式更安全还是独立认证更安全?
|
||||
|
||||
**A**: 取决于网络拓扑:
|
||||
- **内网隔离良好**: Gateway 模式更优(性能好,维护简单)
|
||||
- **微服务直接对外**: 独立认证更安全(每层验证)
|
||||
|
||||
---
|
||||
|
||||
## 推荐配置
|
||||
|
||||
### 生产环境推荐
|
||||
|
||||
```yaml
|
||||
# Gateway
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: false
|
||||
|
||||
# 所有微服务
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true
|
||||
```
|
||||
|
||||
### 开发环境(快速调试)
|
||||
|
||||
可以临时关闭认证:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 对比项 | Gateway 统一认证 | 微服务独立认证 |
|
||||
|--------|-----------------|---------------|
|
||||
| 认证次数 | 1次(仅 Gateway) | N次(每个服务) |
|
||||
| 性能 | ⭐⭐⭐⭐⭐ 最优 | ⭐⭐⭐ 一般 |
|
||||
| 维护性 | ⭐⭐⭐⭐⭐ 统一管理 | ⭐⭐⭐ 分散管理 |
|
||||
| 安全性 | ⭐⭐⭐⭐ 需内网隔离 | ⭐⭐⭐⭐⭐ 多层防护 |
|
||||
| 推荐场景 | 内网微服务架构 | 微服务对外暴露 |
|
||||
|
||||
**推荐使用 Gateway 统一认证模式!**
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
121
urbanLifelineServ/gateway/pom.xml
Normal file
121
urbanLifelineServ/gateway/pom.xml
Normal 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>
|
||||
|
||||
<!-- WebFlux(Gateway依赖) -->
|
||||
<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>
|
||||
@@ -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("========================================");
|
||||
}
|
||||
}
|
||||
@@ -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 配置中心实现动态路由更新
|
||||
*/
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
133
urbanLifelineServ/gateway/src/main/resources/application.yml
Normal file
133
urbanLifelineServ/gateway/src/main/resources/application.yml
Normal 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"
|
||||
@@ -10,6 +10,7 @@
|
||||
<modules>
|
||||
<module>common</module>
|
||||
<module>apis</module>
|
||||
<module>gateway</module>
|
||||
<module>log</module>
|
||||
<module>system</module>
|
||||
<module>auth</module>
|
||||
|
||||
Reference in New Issue
Block a user