397 lines
8.1 KiB
Markdown
397 lines
8.1 KiB
Markdown
|
|
# 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 统一认证模式!**
|