认证总览
认证回答"你是谁"——把请求中的凭证(Token、Cookie、自定义协议)还原成用户身份,写入
HttpContext.user。
核心问题
认证是请求管道的身份识别环节。它不负责判断"你能不能访问"(那是授权的活),只负责搞清楚"你是谁"。
一个典型的请求处理流程:
请求 → useRouting() → useAuthentication() → useAuthorization() → 业务逻辑
↑ ↑
填 context.user 读 context.user
(认证的产出) (授权消费它)认证中间件跑完,请求上就有了 context.user(一个 ClaimsPrincipal)。至于这个用户能不能访问当前接口,交给后面的授权中间件决定。
认证的完整流程
认证中间件 useAuthentication() 执行三步:
- 检查是否有请求级处理器(如 OIDC 登录跳转)——有就截断管道,直接返回
- 取默认认证方案,创建 handler,执行
authenticate()——handler 从请求中读取凭证(Token / Cookie / 自定义头),校验后生成ClaimsPrincipal,写入context.user next(context)——无论认证成功还是失败,请求都继续往下走。认证中间件不返回 401
关键机制:
| 机制 | 说明 |
|---|---|
| Handler 同请求缓存 | 同一个 scheme 的 handler 只初始化一次,避免重复创建 |
handleAuthenticateOnce() | 同实例只执行一次认证逻辑,授权中间件再调时直接返回缓存结果 |
forwardAuthenticate | 可配置转发,当前方案不做认证,委托给目标方案 |
| 认证失败不拦截 | 失败时什么都不写——不设 statusCode,不抛异常 |
深入执行细节
认证中间件的完整执行链路(handler 初始化、认证结果传递、与授权中间件的交互)已整合到本文档中。授权细节见 授权策略。
四种认证方式
Spire 内置四种认证方式,从简单到完整认证中心逐级递进:
| 认证方式 | 一句话描述 | 典型场景 |
|---|---|---|
| JWT Bearer | 从 Authorization: Bearer <token> 头读取并校验 JWT | API、移动端、前后端分离 |
| Cookie | 加密票据写入浏览器 Cookie,请求自动携带 | 浏览器登录态、服务端页面 |
| 自定义Basic认证 | 继承 AuthenticationHandler,自己写读取和校验逻辑 | Basic、HMAC、签名头、网关票据 |
| Identity Server | 独立的 OIDC/OAuth2 认证中心,完整协议端点 | 单点登录(SSO)、多客户端统一认证 |
JWT Bearer
请求头: Authorization: Bearer eyJhbGciOi...
│
▼
JwtBearerHandler
1. 读取 Token
2. 校验签名/issuer/audience/有效期
3. 从 claims 构建 ClaimsPrincipal
│
▼
context.user = principal适合无状态 API。服务端不存会话,每次请求靠 Token 自证身份。
Cookie 认证
POST /login → 验证凭据 → signIn() → Set-Cookie: .spire.auth=<加密票据>
│
GET /profile ──────────────────────────────┘(浏览器自动携带)
│
▼
CookieAuthenticationHandler
1. 读取 Cookie
2. 解密 + 反序列化票据
3. 还原 ClaimsPrincipal
│
▼
context.user = principal适合浏览器登录态。票据可存在 Cookie 里,也可只存会话键、真实票据放服务端。challenge() 默认跳登录页(302)而非返回 401。
自定义Basic认证
三步接入自定义协议:
- 定义
Options(继承AuthenticationSchemeOptions) - 继承
AuthenticationHandler<TOptions>,重写handleAuthenticate() - 通过
extend AuthenticationBuilder暴露addXxx()注册入口
cangjie
// 核心:你只需要写这段
public func handleAuthenticate(): AuthenticateResult {
let header = this.context.request.headers.authorization.first ?? String.empty
// 1. 读凭证
if (header.isEmpty() || !header.startsWith("Basic ")) {
return AuthenticateResult.noResult() // 没有可处理的凭证,跳过
}
// 2. 校验凭证
let credential = parseBasic(header)
if (credential.username == options.username &&
credential.password == options.password) {
// 3. 成功:创建票据
let principal = ClaimsPrincipal([ClaimsIdentity(Some(this.scheme.name))])
return AuthenticateResult.success(AuthenticationTicket(principal, this.scheme.name))
}
return AuthenticateResult.fail("Invalid credentials")
}如何选择
需要独立认证中心(SSO / 多客户端)? → Identity Server
│
否 ↓
客户端是浏览器? → Cookie 认证
│
否 ↓
客户端是 API / 移动端? → JWT Bearer
│
否 ↓
内置方案不满足协议要求? → 自定义Basic认证架构组件速查
认证体系涉及的核心类型:
| 组件 | 角色 |
|---|---|
AuthenticationMiddleware | 请求管道的认证入口,驱动认证流程 |
IAuthenticationService | 统一入口:authenticate() / challenge() / forbid() / signIn() / signOut() |
IAuthenticationSchemeProvider | 方案注册与查询,管理默认方案 |
IAuthenticationHandlerProvider | 从 DI 或缓存中获取 Handler 实例 |
AuthenticationScheme | 一个认证方案(name + handlerType) |
AuthenticationHandler<TOptions> | 认证处理器的抽象基类——你实现 handleAuthenticate() |
AuthenticationBuilder | 注册方案的构建器:addJwtBearer() / addCookie() / addScheme() |
AuthenticateResult | 三种结果:success / fail / noResult |
AuthenticationTicket | 认证产生的结果票据(ClaimsPrincipal + AuthenticationProperties) |
详细 API 参考
组件的方法签名、初始化参数、转发配置等细节,参见 API 文档。
认证 vs 授权
两者在 Spire 中严格解耦,但紧密配合:
| 维度 | 认证 Authentication | 授权 Authorization |
|---|---|---|
| 问题 | 你是谁? | 你能做什么? |
| 中间件 | useAuthentication() | useAuthorization() |
| 产物 | context.user | 放行 / 401 / 403 |
| 失败行为 | 不拦截请求 | 未认证 → challenge() → 401;已认证但权限不够 → forbid() → 403 |
| 校验对象 | Token / Cookie / 自定义凭证 | Endpoint.metadata 中的授权策略 |
| 核心组件 | AuthenticationHandler | AuthorizationHandler |
关键认知:认证中间件不返回 401。 它只负责识别身份。401 由授权中间件发现"用户未认证 + 策略要求认证"时触发。
详细授权流程参见 授权策略。
相关专题
按你的场景选择入口:
还没搞清楚认证和授权的关系 → 回到 安全与身份总览
需要接入 JWT → JWT Bearer 认证
需要浏览器登录态 → Cookie 认证
需要自定义协议 → 自定义Basic认证
需要完整的认证中心 → Identity Server
需要控制访问权限 → 授权策略