微服务鉴权一行配置能搞定?别吹了好吗,你知道小母牛坐飞机-牛逼上天了吗?
好吧!那么本文带你拆解"魔法",用 Spring Cloud Gateway + JWT 实现近乎零侵入的鉴权方案。
作为java后端的牛马,你可能经历过:每个微服务都要重复写鉴权逻辑,@PreAuthorize
注解满天飞,改个权限策略得挨个服务排查。当鉴权逻辑如藤蔓般缠绕在每个服务的入口时,任何微小的权限变更都会有不小的影响。那么有没有办法把鉴权"收拢"到一处?
一、为什么传统鉴权方式在微服务中如此痛苦?
先看两种常见但问题重重的方案:
-
每个服务各自为战 :
在每个服务的 Controller 层添加
@PreAuthorize("hasRole('ADMIN')")
等注解。
痛点:重复代码、维护困难、升级权限策略需修改所有服务。 -
API 网关简单拦截 :
在网关层写死路径匹配规则(如
/api/admin/**
需要 ADMIN 角色)。
痛点:配置冗长、不灵活、无法处理细粒度权限。
二、方案:网关统一鉴权 + 身份透传
核心思想 :在网关层集中完成 JWT 令牌的验证与解析 ,将解析出的用户身份信息 (如用户ID、角色)以请求头的方式传递给下游微服务。下游服务无需重复鉴权,只需信任网关转发的信息即可。
实现关键:一行配置的"灵魂"
在 Spring Cloud Gateway 的配置文件中,你会找到类似这样的"一行配置":
yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
# 关键魔法在这里!
- AuthRelay # 这行配置激活我们的自定义鉴权中继过滤器
解读 :AuthRelay
是一个自定义的 Gateway Filter。它并不直接完成所有鉴权,而是整个流程的"开关"和"搬运工"。
三、拆解:AuthRelay
过滤器做了什么?
java
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class AuthRelayGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
// 1. 从请求头获取JWT令牌
String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // 去掉"Bearer "前缀
// 2. 验证JWT有效性 (此处应调用独立服务或使用本地校验)
if (!JwtUtils.validateToken(token)) {
throw new RuntimeException("Invalid token");
}
// 3. 解析JWT中的Claims (用户信息)
Claims claims = JwtUtils.parseClaims(token);
// 4. 将关键用户信息添加到新的请求头,传递给下游服务
ServerHttpRequest newRequest = 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(newRequest).build());
} else {
// 没有Token或格式错误,直接拦截
throw new RuntimeException("Missing or invalid Authorization header");
}
};
}
}
下游微服务如何消费身份信息?
下游服务(如 user-service
)完全无需再解析 JWT。它直接从请求头获取网关注入的信息:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 获取当前用户ID (从网关添加的请求头)
@GetMapping("/me")
public String getCurrentUser(@RequestHeader("X-User-Id") String userId) {
return "Current User ID: " + userId;
}
// 基于角色的访问控制 (网关已传递角色,服务直接使用Spring Security检查)
@PreAuthorize("hasRole('ADMIN')") // 这里的角色来自请求头 `X-User-Roles`
@GetMapping("/admin")
public String adminEndpoint() {
return "Admin area accessed!";
}
}
关键点 :下游服务的 @PreAuthorize
注解依然有效,但它的角色数据源不再是本地安全上下文,而是网关通过 X-User-Roles
请求头传递过来的、已认证的用户角色列表 。Spring Security 需要额外配置一个 GrantedAuthoritiesMapper
来解析这个头。
四、总结:一行配置的"本质"
所谓的"一行配置搞定" (- AuthRelay
) 实质是 Spring Cloud Gateway 强大过滤器机制的应用 。这行配置激活了一个自定义过滤器 ,该过滤器完成了关键的 JWT 校验、身份解析和信息透传工作 。这个主要是架构设计(网关集中鉴权+身份透传)在具体技术栈(Spring Cloud Gateway)上的简洁方式。
微服务鉴权没有真正的"一行搞定",但是如果是合理的架构的话能让我们的核心配置更加的简洁的。 把复杂度大的东西放在网关层,让业务服务更加轻松专注业务服务层的任务不是更好吗?