文章目录
-
- [1. 架构与职责划分](#1. 架构与职责划分)
-
- [1.1 Auth 服务(认证服务器)](#1.1 Auth 服务(认证服务器))
- [1.2 Gateway(网关 + 资源服务器)](#1.2 Gateway(网关 + 资源服务器))
- [2. 登录请求形态(Password 模式)](#2. 登录请求形态(Password 模式))
- [3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路](#3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路)
-
- [3.1 Basic 校验:进入 `BasicAuthenticationFilter`(发生在 Auth)](#3.1 Basic 校验:进入
BasicAuthenticationFilter(发生在 Auth)) - [3.2 进入 Token 端点:`/oauth/token` 封装用户登录凭证](#3.2 进入 Token 端点:
/oauth/token封装用户登录凭证) - [3.3 `ProviderManager` 循环调用 `AuthenticationProvider`,命中自定义 Provider](#3.3
ProviderManager循环调用AuthenticationProvider,命中自定义 Provider) - [3.4 调用 `UserDetailsService#loadUserByUsername` 加载用户信息](#loadUserByUsername` 加载用户信息)
- [3.5 签发 Token:Access Token 为 JWT,同时写入 Redis](#3.5 签发 Token:Access Token 为 JWT,同时写入 Redis)
- [3.1 Basic 校验:进入 `BasicAuthenticationFilter`(发生在 Auth)](#3.1 Basic 校验:进入
- [4. 前端:缓存 Token 并在后续请求携带](#4. 前端:缓存 Token 并在后续请求携带)
- [5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息](#5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息)
-
- [5.1 Gateway:扩展 `RemoteTokenServices` 调用 `/oauth/check_token`](#5.1 Gateway:扩展
RemoteTokenServices调用/oauth/check_token) - [5.2 Gateway:解析用户信息并写入请求头透传给下游](#5.2 Gateway:解析用户信息并写入请求头透传给下游)
- [5.1 Gateway:扩展 `RemoteTokenServices` 调用 `/oauth/check_token`](#5.1 Gateway:扩展
- [6. 全链路总结(按执行顺序)](#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
- 登录请求:前端先请求 Gateway,由 Gateway 转发到 Auth 的
- 资源服务器鉴权
- 业务请求携带 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=passwordusername=xxxpassword=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 校验(可对接数据库、缓存或配置中心)。
整体过程可以概括为:
BasicAuthenticationFilter解析 Basic Header- 构造
Authentication请求对象(具体类型取决于实现) - 调用
AuthenticationManager.authenticate(...)完成 Client 认证
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 索引信息",以支持后续的主动吊销、强制下线、黑名单等能力
典型响应结构如下:
json
{
"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 提取用户信息写入请求头
→ 分发到下游微服务