文章目录
-
- 1. 架构与职责划分
-
- 1.1 Auth 服务(认证服务器)
- 1.2 Gateway(网关 + 资源服务器)
- 2. 登录请求形态(Password 模式)
- 3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路
-
- 3.1 Basic 校验:进入 `BasicAuthenticationFilter`(发生在 Auth)
- 3.2 进入 Token 端点:`/oauth/token` 封装用户登录凭证
- 3.3 `ProviderManager` 循环调用 `AuthenticationProvider`,命中自定义 Provider
- 3.4 调用 `UserDetailsService#loadUserByUsername` 加载用户信息
- 3.5 签发 Token:Access Token 为 JWT,同时写入 Redis
- 4. 前端:缓存 Token 并在后续请求携带
- 5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息
-
- 5.1 Gateway:扩展 `RemoteTokenServices` 调用 `/oauth/check_token`
- 5.2 Gateway:解析用户信息并写入请求头透传给下游
- 6. 全链路总结(按执行顺序)
本文分享一套在微服务体系中落地认证与鉴权的实现方式:使用
Spring Security + OAuth2(Password密码模式),由
Auth 服务作为认证服务器(Authorization Server),由
Gateway同时承担
网关与
资源服务器(Resource Server)的职责。核心目标是把链路讲清楚:从「前端登录」到「签发 JWT 并写入 Redis」再到「网关校验
Token、透传用户信息」的完整流程,并把
关键过滤器、
Provider、
UserDetailsService、
TokenStore
的调用顺序按实际执行顺序梳理出来,便于理解与排查问题。
适用范围说明(很重要)
本文的实现方式基于 Password Grant(密码模式)。该模式在 OAuth2.1 中已不再推荐,但在企业内部系统里,如果属于自家第一方应用(前端与后端同属一个安全域、可控且强信任),仍然有较多落地案例。本文聚焦工程实现与链路剖析,不讨论协议演进取舍。
1. 架构与职责划分
1.1 Auth 服务(认证服务器)
Auth 服务提供两类能力:
- 签发 Token
- POST /oauth/token:密码模式换取 Token
- 校验 Token
- POST /oauth/check_token:供资源服务器远程校验使用(Token Introspection)
在 Auth 服务中会做一些定制化扩展(不同团队会略有差异):
- 自定义 Client 认证 Provider(例如 ClientAuthenticationProvider):用于 Basic 方式校验 clientId/clientSecret
- 自定义 UserDetailsService#loadUserByUsername:加载用户信息
- 自定义 TokenStore:将 Token 数据写入 Redis(用于会话管理、吊销、风控扩展等)
关键点:Basic 认证发生在 Auth 服务。Gateway 只负责转发登录请求,不承担 client 认证。
1.2 Gateway(网关 + 资源服务器)
Gateway 主要承担三件事:
- 统一入口与路由转发
- 登录请求:前端先请求 Gateway,由 Gateway 转发到 Auth 的 /oauth/token
- 资源服务器鉴权
- 业务请求携带 Token 到达 Gateway 后,Gateway 先校验 Token,再决定是否分发
- 用户上下文透传
- 在 Gateway 内通过过滤器/拦截器从 JWT(或校验结果)中提取用户 ID 等信息,写入请求头透传给下游微服务
2. 登录请求形态(Password 模式)
登录时,前端会向 Gateway 发起请求(随后由 Gateway 转发到 Auth),典型请求如下:
-
Header:
- Authorization: Basic base64(clientId:clientSecret)
- Content-Type: application/x-www-form-urlencoded
-
Body(表单):
- grant_type=password
- username=xxx
- password=yyy
- (可选)scope=all
3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路
下面按实际执行顺序梳理 Auth 服务内发生的事情。
3.1 Basic 校验:进入 BasicAuthenticationFilter(发生在 Auth)
请求头带了:
Authorization: Basic base64(clientId:clientSecret)
因此会先经过 BasicAuthenticationFilter。该过滤器解析 Basic 头后,会把 OAuth2 Client 的认证请求交给 Spring Security 的认证体系处理。
这一阶段的目标是:认证 OAuth2 Client 是否合法(不是认证业务用户)。
此处通过自定义的 ClientAuthenticationProvider 完成 clientId/clientSecret 校验(可对接数据库、缓存或配置中心)。
整体过程可以概括为:
3.2 进入 Token 端点:/oauth/token 封装用户登录凭证
当 Client 认证通过后,请求进入 OAuth2 的 Token Endpoint:/oauth/token。
当请求参数为 grant_type=password 时,框架会读取 username 与 password,并封装为:
- UsernamePasswordAuthenticationToken
随后调用:
- AuthenticationManager.authenticate(usernamePasswordAuthenticationToken)
默认的 AuthenticationManager 实现通常是:
- ProviderManager
3.3 ProviderManager 循环调用 AuthenticationProvider,命中自定义 Provider
ProviderManager 内部维护一组 AuthenticationProvider,会按顺序循环执行:
- supports(authentication.getClass())
- 若支持则调用 authenticate(authentication)
最终会命中自定义的用户认证 Provider(或基于 DaoAuthenticationProvider 扩展的实现),进入用户认证逻辑。
3.4 调用 UserDetailsService#loadUserByUsername 加载用户信息
在自定义 Provider 中会调用:
- UserDetailsService#loadUserByUsername(username)
由该方法完成用户信息加载(查库/缓存/远程服务均可),返回 UserDetails。随后结合 PasswordEncoder 完成密码比对,并检查账户状态(是否锁定、是否过期、是否启用等)。
认证通过后,会生成一个 authenticated=true 的 Authentication,其中包含用户主体信息与权限集合(GrantedAuthorities)。
3.5 签发 Token:Access Token 为 JWT,同时写入 Redis
用户认证成功后,OAuth2 进入 Token 签发流程:
- 生成 OAuth2AccessToken(以及可选的 Refresh Token)
- 通过自定义 TokenStore 写入 Redis
这里的关键约定是:
- 返回给前端的 access_token 是 JWT,其中会包含部分用户字段(例如用户 ID、用户名等 claims)
- Redis 扮演“服务端会话/状态”的角色:用于存储“全量 token 或 token 索引信息”,以支持后续的主动吊销、强制下线、黑名单等能力
典型响应结构如下:
{
"access_token": "xxx.yyy.zzz",
"token_type": "bearer",
"refresh_token": "yyy",
"expires_in": 43199,
"scope": "all"
}
4. 前端:缓存 Token 并在后续请求携带
前端拿到 access_token 后,会写入 sessionStorage(或 localStorage),后续访问业务接口时统一携带:
Authorization: Bearer <access_token>
5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息
5.1 Gateway:扩展 RemoteTokenServices 调用 /oauth/check_token
业务请求到达 Gateway 后,Gateway 作为资源服务器需要先校验 Token。
这里采用 远程校验(Token Introspection):
- 在 Gateway 中扩展/重写 RemoteTokenServices
- 调用 Auth 的 POST /oauth/check_token 校验 token 是否有效
之所以在 access_token 已经是 JWT 的情况下仍然保留 /check_token,核心目的是引入服务端可控性: 即使 JWT 在结构上仍然“可验签且未过期”,也可以通过 Redis/服务端状态实现主动吊销(例如注销、踢下线、密码修改后使旧 token 失效等)。
注意:/oauth/check_token 通常也需要 client 认证(Basic)。因此 Gateway 调用该接口时一般也需要携带 client 的 Basic 认证信息,避免把 introspection 接口暴露为匿名可用。
5.2 Gateway:解析用户信息并写入请求头透传给下游
Gateway 内会实现拦截器/过滤器,用于:
- 从 JWT(或 /check_token 返回的 claims)中提取:
- 用户 ID
- 用户名
- 租户 ID
- 角色/权限等
- 写入请求头透传到下游微服务,例如:
X-User-Id: 10001
X-Username: sfsfsfsdf
X-Tenant-Id: t001
同时会做一个重要约束:网关负责清洗并覆盖这些内部请求头,避免客户端伪造 X-User-Id 等字段;下游服务只信任来自网关的内网请求。
6. 全链路总结(按执行顺序)
-
登录链路 前端请求 Gateway → Gateway 转发 Auth → Auth:BasicAuthenticationFilter 校验 Client(自定义 ClientAuthenticationProvider) → 进入 /oauth/token → 封装 UsernamePasswordAuthenticationToken → AuthenticationManager.authenticate(…)(ProviderManager) → 命中自定义用户 Provider → UserDetailsService#loadUserByUsername → 签发 JWT(自定义 TokenStore 写入 Redis) → 返回 Token 给前端
-
访问链路 前端携带 Authorization: Bearer <token> 请求 Gateway → Gateway:RemoteTokenServices 调 Auth /oauth/check_token 远程校验(用于支持吊销/强制失效) → 校验通过后放行 → Gateway 提取用户信息写入请求头 → 分发到下游微服务



