授权总览
授权回答"你能做什么"——消费认证产出的
context.user,根据端点声明的策略决定放行、401 还是 403。
适用场景
- 需要基于登录态、角色、声明或自定义条件控制接口访问
- 希望端点只声明"需要什么权限",判定规则由授权系统集中管理
- 多认证方案并存时,需要为不同接口指定不同的认证方式
核心问题
授权是请求管道中介于认证和业务逻辑之间的权限判定层。
请求 → useAuthentication() → useAuthorization() → 业务逻辑
↑ ↑
填 context.user 读 context.user + Endpoint.metadata
(认证产出身份) (授权判定权限)
│
┌─────────────┼─────────────┐
▼ ▼ ▼
放行 401 403
(未认证) (已认证但权限不够)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
它与认证的分工明确:
| 维度 | 认证 Authentication | 授权 Authorization |
|---|---|---|
| 问题 | 你是谁? | 你能做什么? |
| 输入 | Token / Cookie / 自定义凭证 | context.user + Endpoint.metadata |
| 输出 | ClaimsPrincipal | 放行 / challenge(401) / forbid(403) |
| 失败行为 | 不拦截,请求继续往下走 | 未认证 → 401,已认证无权限 → 403 |
核心流程
授权中间件 useAuthorization() 分五步执行:
请求进入 AuthorizationMiddleware
│
▼
① 解析策略:从 Endpoint.metadata 提取 IAuthorizeData,组合成 AuthorizationPolicy
│
├── 无策略且无 fallbackPolicy → 直接 next(),跳过授权
│
└── 解析出策略 → 继续
│
▼
② 执行认证:按策略指定的 authenticationSchemes 调 context.authenticate()
│
▼
③ AllowAnonymous?→ next(),授权失败也不拦截
│
▼
④ 执行授权:遍历所有 IAuthorizationHandler 处理 requirements
│
├── pendingRequirements 全部清空 → 放行
│
└── 有残留 →
├── 未认证 → challenge() → 401
└── 已认证 → forbid() → 4031
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
深入执行细节
每一步的源码级拆解见 授权策略 的核心流程章节。
模块地图
授权体系包含三个核心概念,按从外到内的层次组织:
| 层次 | 概念 | 说明 | 详细文档 |
|---|---|---|---|
| 端点层 | IAuthorizeData | 端点通过 requireAuthorization() / allowAnonymous() 声明授权要求 | 授权策略 |
| 策略层 | AuthorizationPolicy | 一组 requirements + authenticationSchemes 的组合,可通过名称引用 | 授权策略 |
| 判定层 | IAuthorizationRequirement + IAuthorizationHandler | 具体的权限判定逻辑,内置 6 种 + 可自定义 | 自定义授权要求 |
架构组件速查
| 组件 | 角色 |
|---|---|
AuthorizationMiddleware | 请求管道的授权入口,驱动策略解析和执行 |
IAuthorizationService | 核心授权逻辑:收束 handler、执行判定、评估结果 |
AuthorizationPolicy | 策略对象:包含 requirements 列表和 authenticationSchemes |
AuthorizationPolicyBuilder | 策略的流式构建器:requireClaim() / requireRole() / requireAuthenticatedUser() |
IAuthorizationRequirement | 授权要求标记接口——"需要满足什么条件" |
IAuthorizationHandler / AuthorizationHandler<T> | 授权处理器——"条件是否满足"的判断逻辑 |
AuthorizationHandlerContext | 处理器上下文:维护 pendingRequirements、succeed() / fail() 状态 |
IPolicyEvaluator | 策略执行器:先调认证、再调授权,输出 PolicyAuthorizationResult |
IAuthorizationMiddlewareResultHandler | 结果处理器:succeeded → next()、challenged → 401、forbidden → 403 |
IAuthorizationPolicyProvider | 策略提供器:按名称或默认/回退规则获取策略实例 |
内置的授权要求
框架提供了 6 种内置 requirement,覆盖常见的权限判定场景:
| Requirement | 用途 | 示例 |
|---|---|---|
DenyAnonymousAuthorizationRequirement | 拒绝匿名用户,要求登录 | requireAuthenticatedUser() |
RolesAuthorizationRequirement | 按角色校验 | requireRole(["admin", "manager"]) |
ClaimsAuthorizationRequirement | 按声明值校验 | requireClaim("age") { age => age.toInt64() >= 18 } |
NameAuthorizationRequirement | 按用户名校验 | requireUserName("spire") |
AssertionRequirement | 自定义断言函数 | 传入 (context) => Bool 委托 |
OperationAuthorizationRequirement | 操作名称标记 | 资源级授权场景 |
所有内置 requirement 都采用自处理模式——requirement 自身同时作为 handler,无需额外 DI 注册。
三种策略层次
端点显式指定策略 → requireAuthorization(["AdminOnly"])
│
▼ 未指定策略名称
defaultPolicy(默认:只要登录即可)
│
▼ 端点无任何授权元数据
fallbackPolicy(默认:None,即跳过授权)1
2
3
4
5
6
7
2
3
4
5
6
7
| 策略 | 默认值 | 行为 |
|---|---|---|
defaultPolicy | requireAuthenticatedUser() | requireAuthorization() 不带参数时使用 |
fallbackPolicy | None | 所有未标注 requireAuthorization() 的端点统一应用的策略 |
| 命名策略 | 无,需手动添加 | 通过 options.addPolicy("名称") 注册,端点上按名称引用 |
应用授权到端点
授权策略最终要挂到端点上才生效。MiniAPI 和 MVC 用了不同的语法,但原理相同——都是在 Endpoint.metadata 中写入 IAuthorizeData 或 IAllowAnonymous。
MiniAPI:链式调用
在端点或分组上直接调用 requireAuthorization() / allowAnonymous():
cangjie
// 单个端点
app.mapGet("/admin") { ctx => ctx.response.write("admin") }
.requireAuthorization(["AdminOnly"])
// 分组统一授权(组内所有端点自动继承)
let admin = app.mapGroup("/admin")
admin.requireAuthorization(["AdminOnly"])
admin.mapGet("/users") { ctx => ctx.response.write("users") } // 自动继承
admin.mapGet("/logs") { ctx => ctx.response.write("logs") } // 自动继承
// 分组内的个别端点豁免
admin.mapGet("/public") { ctx => ctx.response.write("public") }
.allowAnonymous()1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
allowAnonymous() 优先级最高——授权中间件先检查 IAllowAnonymous,发现就直接放行。
MVC:注解
在控制器或动作上标注 @Authorize / @AllowAnonymous:
cangjie
// 控制器级——所有动作继承
@Authorize
public class AdminController <: Controller {
@HttpGet
public func users(): String { "users" } // 继承 @Authorize
@HttpGet
public func logs(): String { "logs" } // 继承 @Authorize
@HttpGet
@AllowAnonymous
public func notice(): String { "notice" } // 覆盖,公开访问
}
// 全局默认授权——在 mapControllers 时统一加锁
app.mapControllers().requireAuthorization(["default"])1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
殊途同归
无论 MiniAPI 的 requireAuthorization() 还是 MVC 的 @Authorize,最终都是往 Endpoint.metadata 写入元数据:
MiniAPI: .requireAuthorization(["AdminOnly"]) ─┐
├──→ Endpoint.metadata
MVC: @Authorize ─┘ ├── IAuthorizeData("AdminOnly")
└── IAllowAnonymous1
2
3
4
2
3
4
授权中间件只读 Endpoint.metadata,不关心元数据是 MiniAPI 链式调用写入的还是 MVC 注解扫描写入的。两种风格可以混用,判定逻辑完全一致。
推荐阅读顺序
当前边界
- 已落地:完整的策略+requirement+handler 模型,6 种内置 requirement,两种自定义模式(自处理/分离),多认证方案指定,策略缓存
- 已支持但文档未展开:
IAuthorizationPolicyProvider完全自定义策略源、IAuthorizationMiddlewareResultHandler自定义 401/403 响应格式、IAuthorizationEvaluator自定义结果评估规则、AuthorizationResourceHandler<TReq, TRes>资源级授权基类 - 当前未覆盖:资源级授权(Resource-based Authorization)的完整落地案例