大家好,我是阿喵。
前两天在群里聊到微服务权限问题,不少朋友对 "网关层怎么做鉴权" 以及 "用户信息怎么在服务间无感传递" 非常头疼。之后, 我在小红书发布了一篇文章《Gateway+OAuth2 + JWT + RBAC实现动态鉴权》,然后看到有朋友想要代码。
微服务架构中,认证(Authentication)和鉴权(Authorization)是真正的"深水区"。很多网上的 Demo 只有简单的登录,涉及到跨服务调用(Feign)或者细粒度的方法级权限控制,全流程的很少。
我就基于 Spring Boot 3 + Spring Security + OAuth2 + Gateway 搭建了一套 RBAC 企业级认证授权平台 的过程整理了出来
不讲虚的,直接上干货!文末有完整源码获取方式。
01 为什么要这么设计?架构总览
在单体应用中,我们习惯用 Session;但在微服务容器化环境下,JWT(JSON Web Token) 才是无状态认证的最佳选择。
我们的核心架构设计思路如下:
- 统一入口(Gateway) :作为"保安",负责校验 JWT 的合法性,拦截非法请求。
- 独立认证(Auth Service) :发放"通行证"(Token),负责复杂的 OAuth2 流程。
- 业务解耦(Business Service) :业务服务不处理复杂的解密逻辑,只管"拿来即用"。
- 无感透传:利用 HTTP Header 和 ThreadLocal,让用户信息在链路中自由流转。
服务清单(基于 Spring Boot 3.2.4)
| 服务 | 端口 | 核心职责 |
|---|---|---|
| gateway-service | 8080 | 网关,GlobalFilter 全局鉴权 |
| auth-service | 9001 | 认证中心,颁发 JWT |
| business-service | 9003 | 业务服务,演示 @PreAuthorize |
02 核心难点一:网关层的"验票"逻辑
网关是流量的咽喉。如果每个请求都在这里查数据库,性能会直接崩盘。所以我们采用 JWT + 内存校验 的方式。
在 gateway-service 中,我们定义一个全局过滤器 JwtAuthFilter:
Java
scss
// 核心代码片段:网关过滤器
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 拦截请求,提取 Token
String token = extractToken(exchange.getRequest());
// 2. 校验 Token 签名是否合法(纯内存操作,高性能)
if (!jwtUtil.validateToken(token)) {
return unauthorized(exchange); // 401
}
// 3. 关键一步:解析用户信息,放入请求头 Header 中
// 这样下游服务就不需要再次解析 Token,直接读 Header 即可
Claims claims = jwtUtil.parseToken(token);
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-Name", claims.getSubject())
.header("X-User-Roles", claims.get("roles"))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
思考:为什么这里用 WebFlux?
传统 Servlet 是线程阻塞模型,1000 个连接可能卡死 1000 个线程。而 Gateway 基于 Netty + WebFlux 响应式编程,少量线程即可处理海量并发,非常适合网关这种 IO 密集型场景。
03 核心难点二:用户信息流转(ThreadLocal 的妙用)
很多朋友会想问: "我在 Controller 里怎么拿到当前登录的用户 ID?"
难道要在每个方法参数里都写 String userId 吗?太 Low 了。
我们设计了一个三层流转模型:
- 跨进程(网关 -> 服务) :使用 HTTP Header (
X-User-Name) 传递。 - 进程内(Filter -> Controller) :使用 ThreadLocal(SecurityContext) 存储。
- 服务间(Feign 调用) :使用 FeignInterceptor 拦截器重新填入 Header。
下游服务的通用拦截器实现:
Java
scala
@Component
public class GatewayAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, ...) {
// 1. 从 Header 读取网关透传过来的数据
String username = request.getHeader("X-User-Name");
// 2. 存入 ThreadLocal (SecurityContext)
if (username != null) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
}
try {
chain.doFilter(request, response);
} finally {
// 3. 必须清理!否则线程池复用会导致数据污染
SecurityContextHolder.clearContext();
}
}
}
在业务代码中调用:
Java
less
// 就像在单体应用一样简单
@GetMapping("/list")
@PreAuthorize("hasAuthority('goods:list')") // 注解鉴权
public List<Goods> list() {
// 随处获取当前用户
String currentUser = UserContext.getUsername();
return service.findByUser(currentUser);
}
04 避坑指南(经验之谈)
在开发这套系统时,我踩过几个典型的坑,分享给大家:
-
Q: 为什么下游服务还要再写一遍 Filter?
- A: 因为网关和下游服务是两个独立的 JVM 进程。网关的 ThreadLocal 传不到下游,只能通过 HTTP Header 作为桥梁。
-
Q: Feign 调用为什么会丢失用户信息?
- A: Feign 默认发出的新请求是不带原始 Header 的。必须实现
RequestInterceptor,手动把 ThreadLocal 里的用户信息塞到 Feign 的请求头里。
- A: Feign 默认发出的新请求是不带原始 Header 的。必须实现
-
Q: Spring Boot 3 的变化?
- A: 以前的
javax.servlet包全变成了jakarta.servlet,Spring Security 6 的配置写法也大变样(废弃了WebSecurityConfigurerAdapter),升级的时候要格外小心依赖版本。
- A: 以前的
05 源码领取 & 后续计划
微服务安全是一个庞大的话题,这篇通过 RBAC + JWT 搭建了一个标准骨架。
为了方便大家学习,我把:
- 完整的 SQL 脚本(包含用户/角色/权限表)
- Docker 部署说明
- 前后端联调 Postman 文件
- 四个微服务的完整源码
全部打包好了。
👉 获取方式:
关注公众号 喵了个Code,后台回复关键词 【Security实战】,即可获取 GitHub/Gitee 仓库地址。
🚀 下一步计划:
现在的配置还是稍显繁琐,我正在计划把这套逻辑封装成一个 Spring Boot Starter。未来大家只需要在 pom.xml 里引入一个依赖,就能自动拥有这套企业级鉴权能力!
如果你对"如何造轮子/写 Starter"感兴趣,欢迎点赞、在看,告诉我你想看!