跳到主要内容
版本: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.cs(ComponentRegister 实现)

基础授权负责「怎么拦、拦什么、权限怎么缓存」,但不内置权限来源。要真正按资源列表做授权判断,还需要配一个 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 拼接后比对
RequestMethodstring是HTTP 方法,与请求 Method 拼接后比对
RequestPathstring否死字段,当前实现不读取
CanAccessbool是是否允许访问;false 或找不到都会触发 403

RequestPath 是历史遗留字段,ApiAuthorize 的比对逻辑只读 Uri 和 RequestMethod。如果业务侧在 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.Value 和 ExpireAt。这意味着客凭请求里 CurrentUser.Value 可能是 null,业务代码不能假设所有经过 ApiAuthorize 的请求都有当前用户。客凭 Token 的有效期由 IAuthorizationManager 自己管理。

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

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

ResourcePermission.RequestPath 是死字段​

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

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

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

常见问题​

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

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

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

检查这几项:

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

为什么 Authentication 和 Authorization 的顺序不能反过来?​

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