认证授权系统设计
问题
如何设计一个统一的认证授权系统?
答案
认证方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Session | 服务端存储会话 | 安全、可控 | 分布式需共享 Session |
| JWT | 客户端存储 Token | 无状态、跨服务 | 无法主动失效 |
| OAuth 2.0 | 授权框架 | 标准、第三方登录 | 流程复杂 |
JWT + 双 Token 方案
JWT 生成与验证
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
public String generateAccessToken(Long userId, List<String> roles) {
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
}
}
RBAC 权限模型
Spring Security 权限校验
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
@PreAuthorize("hasAuthority('order:export')")
@GetMapping("/orders/export")
public void exportOrders(HttpServletResponse response) {
orderService.export(response);
}
网关统一鉴权
Spring Cloud Gateway 鉴权过滤器
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 白名单路径不鉴权
if (isWhiteListed(exchange.getRequest().getPath().toString())) {
return chain.filter(exchange);
}
if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
try {
Claims claims = jwtService.parseToken(token.substring(7));
// 将用户信息放入请求头,传递给下游服务
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Roles", String.join(",", claims.get("roles", List.class)))
.build();
return chain.filter(exchange.mutate().request(request).build());
} catch (Exception e) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder() { return -100; }
}
常见面试问题
Q1: JWT 如何实现主动失效(踢人下线)?
答案:
- 黑名单:将失效的 JWT 放入 Redis 黑名单,每次校验时查黑名单
- 版本号:Redis 存用户 Token 版本号,JWT 中带版本号,不匹配则拒绝
- Access Token 短有效期(30 分钟)可减少黑名单数量
Q2: 如何防止 Token 泄露?
答案:
- Access Token 短有效期
- Refresh Token 仅在刷新接口使用,且绑定设备指纹
- HTTPS 传输
- HttpOnly Cookie 存储(防 XSS 窃取)
Q3: 微服务间如何传递用户信息?
答案:
网关验证 Token 后,将用户 ID、角色等信息放入请求头(X-User-Id),下游服务直接从请求头获取,无需重复验签。