统一网关登录流程的面试要点,这是微服务架构中非常核心的高频考点。
🏗️ 整体架构图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 客户端 │ │ API Gateway │ │ 认证中心 │ │ 下游微服务 │
│ (Web/App) │◄────►│ (统一网关) │◄────►│ (Auth Server)│◄────►│ (业务服务) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
┌─────┴─────┐
│ Redis │ ← Token黑名单/会话缓存
└───────────┘
🔐 核心登录流程(3种主流模式)
模式一:网关集中式认证(最常用)
流程步骤:
- 用户登录 → 请求网关
/auth/login→ 转发到认证中心 - 身份验证 → 认证中心校验用户名密码 → 生成 JWT Token(Access Token + Refresh Token)
- Token返回 → 网关返回 Token 给客户端
- 业务请求 → 客户端携带
Authorization: Bearer <token>请求网关 - 统一鉴权 → 网关全局过滤器校验 JWT(签名、过期时间)
- 身份传递 → 网关将用户信息(UserID、Roles)写入请求头,转发给下游服务
- 业务处理 → 下游服务根据请求头中的用户信息进行权限判断和业务处理
核心代码(Spring Cloud Gateway):
java
@Component
@Order(-1) // 最高优先级执行
public class JwtAuthenticationFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
// 1. 白名单放行(登录、注册、健康检查)
if (isPublicPath(path)) {
return chain.filter(exchange);
}
// 2. 提取 Token
String token = extractToken(request);
if (token == null) {
return unauthorized(exchange, "缺少Token");
}
// 3. 校验 Token(签名、过期时间、黑名单)
try {
DecodedJWT jwt = JWTVerifier.verify(token);
// 4. 将用户信息传递给下游服务
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-Id", jwt.getSubject())
.header("X-User-Roles", jwt.getClaim("roles").asString())
.header("X-User-Name", jwt.getClaim("username").asString())
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (TokenExpiredException e) {
return unauthorized(exchange, "Token已过期");
} catch (JWTVerificationException e) {
return unauthorized(exchange, "Token无效");
}
}
}
模式二:OAuth2 + JWT 授权码模式(第三方登录场景)
流程步骤 :
- 授权请求 → 客户端重定向到认证中心
/oauth2/authorize - 用户登录 → 用户在认证中心完成登录并授权
- 授权码返回 → 认证中心返回 Authorization Code 给客户端
- 换取Token → 客户端携带 Code 请求网关
/oauth2/token - 颁发Token → 网关返回 Access Token (JWT)+ Refresh Token(Opaque Token)
- 后续请求 → 同模式一,网关统一校验 JWT
Token 类型区别:
| Token 类型 | 格式 | 有效期 | 用途 |
|---|---|---|---|
| Access Token | JWT(自包含) | 短(15-30分钟) | 访问资源 |
| Refresh Token | Opaque(随机串) | 长(7-30天) | 刷新 Access Token |
| ID Token | JWT | 同 Access Token | 包含用户基本信息(OIDC) |
模式三:Session + Cookie 模式(传统 Web 场景)
流程步骤 :
- 用户登录 → 网关验证凭证后创建 Session,存储在 Redis
- Cookie返回 → 网关设置
Set-Cookie: SESSION_ID=xxx给浏览器 - 后续请求 → 浏览器自动携带 Cookie,网关从 Redis 查询 Session 获取用户信息
- 信息传递 → 网关将用户信息写入请求头转发
适用场景:传统服务端渲染 Web 应用,需要服务端控制会话状态
🛡️ 面试必问:网关统一认证 vs 服务自认证
| 维度 | 网关统一认证(推荐) | 各服务自认证 |
|---|---|---|
| 安全性 | 统一安全策略,所有请求必须过网关 | 容易遗漏,策略不一致 |
| 性能 | 避免无效请求进入内网 | 重复解析 JWT,浪费资源 |
| 开发成本 | 服务无感知,专注业务 | 每个服务都要实现认证逻辑 |
| 灵活性 | 需要网关支持自定义规则 | 各服务可定制 |
| 运维复杂度 | 日志集中,便于审计 | 日志分散,难以追踪 |
面试金句 :"网关统一认证实现了认证边界的内移,在架构入口处建立单一可信边界,符合零信任架构的'永不信任,始终验证'原则"
🔄 Token 刷新机制(双 Token 策略)
为什么需要刷新?
- Access Token 有效期短(15分钟),降低泄露风险
- Refresh Token 有效期长(7天),但只用于换 Token,不直接访问资源
刷新流程:
java
// 伪代码:网关过滤器中实现 Token 自动刷新
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String accessToken = extractToken(request);
try {
verifyToken(accessToken);
return chain.filter(exchange);
} catch (TokenExpiredException e) {
// Token 过期,尝试用 Refresh Token 刷新
String refreshToken = extractRefreshToken(request);
if (refreshToken != null && validateRefreshToken(refreshToken)) {
// 生成新 Access Token
String newAccessToken = generateNewToken(refreshToken);
// 返回新 Token(通过 Response Header 或 Body)
exchange.getResponse().getHeaders().add("X-New-Token", newAccessToken);
// 继续处理当前请求
return chain.filter(exchange);
} else {
return unauthorized(exchange, "登录已过期,请重新登录");
}
}
}
🚨 安全加固要点(面试加分项)
1. Token 黑名单机制(主动登出)
java
@Service
public class TokenBlacklistService {
@Autowired
private StringRedisTemplate redisTemplate;
// 登出时将 Token 加入黑名单(TTL 设为 Token 剩余有效期)
public void addToBlacklist(String token, long expirationTime) {
String jti = JWT.decode(token).getId(); // JWT ID
long ttl = expirationTime - System.currentTimeMillis();
redisTemplate.opsForValue().set("blacklist:" + jti, "1", ttl, TimeUnit.MILLISECONDS);
}
public boolean isBlacklisted(String token) {
String jti = JWT.decode(token).getId();
return redisTemplate.hasKey("blacklist:" + jti);
}
}
2. 关键安全策略
| 策略 | 实现方式 |
|---|---|
| HTTPS 强制 | 网关层强制 HSTS,拒绝 HTTP 请求 |
| Token 存储 | Access Token 存内存(Redux/Vuex),Refresh Token 存 HttpOnly Cookie |
| 敏感操作二次认证 | 支付/修改密码等操作要求重新输入密码或短信验证 |
| 限流防刷 | 登录接口限流(如 5次/分钟),防止暴力破解 |
| 密钥轮换 | 定期更换 JWT 签名密钥,旧密钥保留一段时间用于验证旧 Token |
3. 性能优化
java
// JWT 解析结果缓存(Caffeine)
@Component
public class JwtCacheManager {
private final Cache<String, DecodedJWT> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
public DecodedJWT verifyWithCache(String token) {
return cache.get(token, t -> JWTVerifier.verify(t));
}
}
💼 面试高频问题 & 标准答案
Q1:网关鉴权失败,如何设计降级策略?
答:熔断降级设计:
- 认证中心不可用时,网关允许已缓存的公钥继续验证 Token(JWT 自包含特性)
- 极端情况下可降级为只限流不鉴权,保证核心业务可用
Q2:如何实现"踢人下线"功能?
答:三种方案:
- Token 黑名单:登出时将 JTI(Token ID)存入 Redis,网关校验时检查
- 修改密钥:强制所有 Token 失效(影响所有用户,慎用)
- Session 模式:直接删除 Redis 中的 Session 记录
Q3:网关如何处理下游服务的权限细化?
答 :网关负责粗粒度认证 (校验 Token 有效性),下游服务负责细粒度授权(判断用户是否有权操作某条数据)。网关传递用户角色/权限标识,服务内部做 RBAC/ABAC 判断 。
Q4:JWT 泄露了怎么办?
答:短期泄露风险可控(Token 有效期短),长期:
- 立即将 Token 加入黑名单
- 轮换签名密钥
- 强制用户重新登录(清除 Refresh Token)
🎯 架构选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 纯内部微服务 | 网关 + JWT | 无状态,性能好 |
| 涉及第三方登录 | OAuth2 + JWT | 标准协议,生态成熟 |
| 高安全要求金融场景 | 网关 + OAuth2 + mTLS | 双向证书 + Token 双重验证 |
| 遗留系统改造 | 网关适配层 | 逐步迁移,兼容旧 Session |
面试总结一句话 :"统一网关登录的核心是集中认证、分散授权------网关在入口处建立信任边界,通过无状态的 JWT 传递身份信息,既保证安全又实现服务间的解耦。"