Files
urbanLifeline/docs/网关认证方案.md
2025-12-02 18:46:03 +08:00

397 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
---
### **模式二:微服务独立认证**
每个微服务独立验证 JWTGateway 不做认证。
#### 架构流程
```
浏览器
↓ (带 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 统一认证模式!**