如何管理Access Token大量权限?解决请求头过长及ASP.NET Core授权问题
问题背景
调用API时在请求头携带Access Token出现「Request header is too long」错误,原因是Token中包含约15KB的大量权限数据,且不建议在Access Token中存储权限。
当前Access Token示例(带中文注释)
{ "roles": [ // 角色 "Admin" ], "iss": "Issuer", // 签发者 "sub": "sub", // 用户唯一标识 "aud": [ // 受众(目标API) "https://example.com/api", "https://example.com/userinfo" ], "iat": 1666198659, // 签发时间戳 "exp": 1666205859, // 过期时间戳 "azp": "azp", // 客户端ID "scope": "openid profile email offline_access", // 授权范围 "org_id": "company1", // 组织ID "permissions": [ // 权限列表 "permission.1", "permission.2", ........ "permission.150" ] }
1. API端认证与授权的最佳方案
(1)用Reference Token替代JWT
放弃把权限塞进JWT的做法,改用Reference Token——这只是一个短字符串,指向授权服务器存储的完整用户权限数据。API收到Token后,向授权服务器发起验证请求获取权限信息,从根源解决请求头过长问题,且权限变更能实时生效。
(2)分离身份认证与权限查询
用轻量JWT做身份认证(只存sub、iss、exp等核心字段),API通过sub(用户ID)或org_id(组织ID),从专门的权限服务/数据库查询用户权限。搭配Redis等分布式缓存存储权限数据,在Token有效期内复用,减少重复查询开销。
(3)RBAC+权限分组优化
如果必须在Token中带权限相关信息,不要存所有细粒度权限:
- 用
roles字段控制大权限范围; - 把细粒度权限分组,Token中只存权限组标识,API再根据组标识映射具体权限。
(4)使用令牌Introspection机制
API通过授权服务器提供的Introspection端点,传入Access Token获取完整的用户授权信息。适合已有JWT体系但需要剥离权限的场景,保证Token轻量化的同时能获取必要权限。
2. ASP.NET Core API中获取用户权限的替代方式
(1)调用授权服务器的Introspection端点
通过IdentityServer4.AccessTokenValidation中间件配置Introspection验证,自动从授权服务器拉取权限并添加到ClaimsPrincipal中。核心代码示例:
services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "https://your-auth-server"; options.ApiName = "your-api-name"; options.ApiSecret = "your-api-secret"; options.EnableIntrospection = true; });
(2)从本地数据库/缓存读取权限
验证Token有效性后,通过User.FindFirst(ClaimTypes.NameIdentifier)获取用户ID,直接查询本地数据库或分布式缓存中的权限数据,再将权限添加到ClaimsPrincipal。可在自定义认证中间件或IAuthorizationHandler中实现。
(3)调用UserInfo端点
如果采用OpenID Connect协议,API用Access Token调用授权服务器的UserInfo端点,获取包含权限的扩展用户信息。需提前在授权服务器配置对应scope(比如permissions),确保返回权限数据。
(4)自定义授权策略与权限服务
实现自定义IAuthorizationHandler,在处理授权逻辑时实时调用权限服务获取用户当前权限,判断是否允许访问接口。示例代码:
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { private readonly IPermissionService _permissionService; public PermissionHandler(IPermissionService permissionService) { _permissionService = permissionService; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); var hasPermission = await _permissionService.HasPermissionAsync(userId, requirement.PermissionName); if (hasPermission) { context.Succeed(requirement); } } }
(5)使用ClaimsTransformation扩展
通过IClaimsTransformation接口,在认证完成后动态加载权限并添加到用户Claims中,后续接口可直接通过User.Claims获取权限:
public class PermissionClaimsTransformation : IClaimsTransformation { private readonly IPermissionService _permissionService; public PermissionClaimsTransformation(IPermissionService permissionService) { _permissionService = permissionService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); var permissions = await _permissionService.GetUserPermissionsAsync(userId); var claimsIdentity = new ClaimsIdentity(); foreach (var permission in permissions) { claimsIdentity.AddClaim(new Claim("permission", permission)); } principal.AddIdentity(claimsIdentity); return principal; } }
内容的提问来源于stack exchange,提问作者ahsan kamal

