跳到主要内容
版本:3.1

基础授权(Aegis.Core.Authorization)

Aegis 的授权体系分两层:本组件提供 ApiAuthorize 拦截器和权限缓存,具体的权限来源(如 ESS)由子组件补齐。基础授权依赖基础认证先建立当前用户上下文,因此不能单独使用。

组件概览

字段说明
组件名称基础授权
真实类库Aegis.Core.Authorization
父级组件Aegis.Core.Authentication
子级扩展Aegis.Authorization.ESS 或自定义 IAuthorizationManager 实现
组件定位提供授权过滤器、资源权限缓存、ApiAuthorize 特性和授权上下文抽象
引入方式Component.deps.json
组件声明Authorization
核心能力ApiAuthorize、权限缓存、资源权限抽象、当前用户扩展字典
是否可扩展
目标框架net8.0
注册入口ServiceCollectionExtensions.csComponentRegister 实现)

基础授权负责「怎么拦、拦什么、权限怎么缓存」,但不内置权限来源。要真正按资源列表做授权判断,还需要配一个 IAuthorizationManager 的具体实现(如 ESS)。

如何引入

NuGet 包

角色NuGet 包是否必需说明
认证基础层Aegis.Core.Authentication基础授权依赖当前用户上下文
授权基础层Aegis.Core.Authorization提供授权过滤和资源权限缓存
具体实现Aegis.Authorization.ESS 或自定义实现按需提供真正的权限来源

Component.deps.json

基础授权既有服务注册,也有中间件加载,且显式依赖 Authentication

{
"Components": {
"Services": [
"Authentication",
"Authorization",
"EssAuthorize"
],
"Middlewares": [
"Authentication",
"Authorization",
"EssAuthorize"
]
}
}
  • Authentication:建立当前用户和认证链路
  • Authorization:加载授权服务和 UseAuthorization()
  • EssAuthorize:提供真实的权限来源与客凭校验能力

只接入 Authorization 而没有 Authentication,组件顺序校验会直接失败。

配置说明

节点类型是否必填说明常见取值 / 示例
Auth:EnableAuthorizationbool建议是是否启用授权校验;关闭后 ApiAuthorize 会直接放行true

最小配置示例:

{
"Auth": {
"EnableAuthorization": true
}
}

快速接入

第一步:按顺序接入认证和授权

  • 安装 Aegis.Core.Authentication
  • 安装 Aegis.Core.Authorization
  • Component.deps.json 中先写 Authentication,再写 Authorization

第二步:在接口上使用 ApiAuthorize

using Aegis.Core.Authentication;
using Aegis.Core.Authorization.Filters;
using Aegis.Core.Infrastructure.Controller;
using Microsoft.AspNetCore.Authorization;

[ApiAuthorize]
public class OrdersController : ApiControllerBase
{
[HttpGet("me")]
public object Me()
{
return CurrentUser.Value;
}

[AllowAnonymous]
[HttpGet("ping")]
public string Ping()
{
return "ok";
}
}

第三步:确认授权链路生效

  • 未带 Token 访问受保护接口时返回 401
  • 标记了 AllowAnonymous 的接口不会被 ApiAuthorize 拦截
  • 注册了具体授权实现后,没有权限访问时返回 403

核心能力

ApiAuthorize 保护接口

ApiAuthorize 是 Aegis 的授权拦截器,标记在 Controller 或 Action 上。请求到达时,它会检查当前用户是否有权访问该接口,没有权限返回 403。

[ApiAuthorize]                          // 整个 Controller 都需要授权
public class OrdersController : ApiControllerBase
{
[HttpGet("me")]
public object Me() => CurrentUser.Value;

[AllowAnonymous] // 单独放行匿名访问
[HttpGet("ping")]
public string Ping() => "ok";
}

不标记 ApiAuthorize 的接口不受授权管控,只要认证通过(或匿名)就能访问。

ApiAuthorize 的判断顺序固定为 5 步:

  1. 检查 Auth:EnableAuthorization,关闭则直接放行
  2. 检查是否存在 ClientAuthorization 请求头,标记当前请求是否为「客凭访问」
  3. 检查 AllowAnonymous(Filter、EndpointMetadata、Action/Controller 特性三处),标记了则放行
  4. 非客凭请求确认 CurrentUser.Value 是否存在、是否过期
  5. 取权限列表,查 Method|Path 是否允许,无权限返回 403

没有具体授权实现时,基础授权只能完成「受保护接口拦截」,不能完成真正的资源权限判定。

资源权限缓存

注册了具体授权实现(如 ESS)后,ApiAuthorize 每次拦截都需要查权限列表。为了避免每个请求都打远端权限服务,基础授权默认把权限缓存到内存,20 分钟过期。同一个 Token 在缓存有效期内复用已取回的权限。

需要跨实例共享权限缓存或自定义缓存时长时,替换 IResourcePermissionManager,见 扩展与自定义

当前用户扩展字典

扩展字典是挂在 CustomUser 上的临时存储,用来在一次请求周期内暂存业务数据。典型场景:在过滤器或中间件里提前解析好组织、数据权限范围等信息,避免 Controller 里重复查询。

using Aegis.Core.Authorization.Extensions;

// 写入(通常在过滤器或中间件里)
CurrentUser.Value.Set("org", new OrgInfo { Id = 1, Name = "技术部" });
CurrentUser.Value.Set("dataScope", "self");

// 读取(在 Controller 或 Service 里)
var org = CurrentUser.Value.Get<OrgInfo>("org");
var dataScope = CurrentUser.Value.Get<string>("dataScope");

客凭访问

当本系统需要以「客户端身份」调用其他受保护服务时,使用 ClientAuthorization 请求头携带客凭 Token。客凭请求会跳过当前用户检查(CurrentUser.Value 可能为 null),但仍走完整的权限比对流程。客凭 Token 的有效期由 IAuthorizationManager 自己管理。

客凭能力需要配合 ESS 组件使用,详见 ESS 认证鉴权

授权拦截链路

API 参考

IAuthorizationManager

权限来源抽象。基础授权不提供默认实现,需要由 ESS 或业务自定义实现注册。

方法参数返回值语义
GetResourcePermissionsstring tokenTask<IEnumerable<ResourcePermission>>按 Token 返回该 Token 能访问的资源权限列表

这个方法只在缓存未命中时被调用。

IResourcePermissionManager

权限缓存抽象。控制权限「怎么缓存、缓存多久、按什么 key 隔离」。

方法参数返回值语义
GetResourcePermissionstring resourceId, string keyResourcePermission按 key 和 resourceId 取单条权限
HasResourcePermissionstring resourceIdbool当前用户的权限集合中是否包含某 resourceId
SetResourcePermissionsIEnumerable<ResourcePermission>, string keyvoid批量写入权限(覆盖式,20 分钟过期)
GetResourcePermissionsstring keyIEnumerable<ResourcePermission>按 key 取整组权限;缓存未命中返回 null,空集合返回空列表
SetResourcePermissionstring resourceId, ResourcePermissionResourcePermission在当前用户权限集合中追加或覆盖单条
RemoveResourcePermissionstring resourceIdbool在当前用户权限集合中移除单条

GetResourcePermissions 返回 null 表示缓存未命中(应去 IAuthorizationManager 取权限并回填);返回空集合表示缓存已命中但该 Token 确实没有任何权限(防穿透)。

ResourcePermission

权限比对的核心数据结构。

字段类型是否参与比对说明
Uristring资源路径,与请求 Path 拼接后比对
RequestMethodstringHTTP 方法,与请求 Method 拼接后比对
RequestPathstring死字段,当前实现不读取
CanAccessbool是否允许访问;false 或找不到都会触发 403

RequestPath 是历史遗留字段,ApiAuthorize 的比对逻辑只读 UriRequestMethod。如果业务侧在 IAuthorizationManager 里只设置了 RequestPath,权限比对会失败。

扩展与自定义

扩展点接口默认实现改变的行为
权限来源IAuthorizationManager由具体实现提供(如 ESS)资源权限列表的获取方式
权限缓存IResourcePermissionManagerDefaultResourcePermissionManager(内存,20 分钟)缓存位置、缓存策略、缓存 key 规则
资源管理IResourceManagerDefaultResourceManager当前所有方法均抛 NotImplementedException,不可直接使用资源元数据的维护

替换方式是在 Startup 中用 DI 覆盖默认实现,例如:

services.AddSingleton<IAuthorizationManager, 你的实现>();

IResourceManager 虽然是公开扩展点,但默认实现 DefaultResourceManager 当前未完成(所有方法抛 NotImplementedException)。ApiAuthorize 的运行时不依赖 IResourceManager,不实现它不影响权限拦截链路。

完整的自定义实现步骤和代码模板见 自定义认证鉴权实现指南

边界与陷阱

过期返回 200 不是 401

user.ExpireAt 已过期时,ApiAuthorize 返回 HTTP 200 + Code = TokenExpired,而不是 401。前端拦截响应时需要同时检查 HTTP 状态码和业务 Code

  • HTTP 401 + Code = UnAuthenticate:完全未登录
  • HTTP 200 + Code = TokenExpired:登录已过期,应触发刷新或重新登录
  • HTTP 403 + Code = NotAllowed:已登录但无权限

只按 HTTP 状态码判断会把「过期」误判为「成功」。

客凭请求跳过用户和过期检查

ClientAuthorization 头的请求不会检查 CurrentUser.ValueExpireAt。这意味着客凭请求里 CurrentUser.Value 可能是 null,业务代码不能假设所有经过 ApiAuthorize 的请求都有当前用户。客凭 Token 的有效期由 IAuthorizationManager 自己管理。

权限缓存命中后 20 分钟内不刷新

DefaultResourcePermissionManager 的缓存时长固定 20 分钟。缓存命中期间,即使权限中心已经更新了该用户的权限,应用层也不会感知。需要立即生效的场景可以替换 IResourcePermissionManager 缩短缓存时长,或在权限变更后主动调用 RemoveResourcePermission 清除缓存。

ResourcePermission.RequestPath 是死字段

ApiAuthorize 比对时只用 UriRequestMethod。业务侧构造 ResourcePermission 时必须设置 Uri,否则权限比对会失败。

权限缓存按 token 而非用户隔离

缓存 key 是 token,同一个用户用不同 Token 登录会产生多个缓存项。短期内同一用户的权限变更需要清所有相关 Token 的缓存。

常见问题

为什么加了 Authorization 以后,接口还是没有真正按权限拦截?

基础授权只负责授权框架,不负责权限来源。要实现真正的资源权限校验,还需要接入 Aegis.Authorization.ESS 或自己的 IAuthorizationManager

为什么 ApiAuthorize 没有拦住某个接口?

检查这几项:

  • Auth:EnableAuthorization 是否为 true
  • 当前接口或控制器是否标记了 AllowAnonymous
  • Authorization 是否已经加入 Middlewares
  • 是否已经注册了具体授权实现

为什么 AuthenticationAuthorization 的顺序不能反过来?

授权判断依赖 CurrentUser。只有认证先把用户写入上下文,授权层才能继续判断是否允许访问。