ASP.NET Core中避免JwtBearerEvents递归获取Access Token的方法
场景说明
我正在构建一个采用Azure Entra ID(原Azure AD)认证的ASP.NET Core API,需要在JwtBearerEvents的TokenValidated事件中处理组超限场景。为了获取用户的Access Token调用Microsoft Graph查询组信息,我通过HttpContext获取Token,但调用GetTokenAsync会触发TokenValidated事件,导致递归循环。
当前实现代码
JwtBearerEvents处理器
public class JwtBearerEventsProcessor(IGroupOverageService groupOverageService) : JwtBearerEvents { public override async Task TokenValidated(TokenValidatedContext context) { if (context != null) { // 处理组超限(调用方法处理Claims) await groupOverageService.ProcessAnyGroupOverage(context); } await Task.CompletedTask; } }
获取Access Token的方法
public async Task<string> GetAccessToken() { if (_httpContextAccessor.HttpContext == null) { throw new InvalidOperationException("HttpContext未初始化!"); } // FIXME: 此调用会触发TokenValidated,导致递归 var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token"); if (string.IsNullOrWhiteSpace(accessToken)) { throw new InvalidOperationException("无法从HttpContext获取Access Token!"); } return accessToken; }
核心问题
调用GetTokenAsync时会再次触发TokenValidated事件,形成无限递归循环。
疑问
- 如何安全获取Access Token而不触发中间件管道或递归?
- 有没有更优的组超限处理方式?
临时解决方案
放弃自定义JwtBearerEvents,改用自定义中间件调用IGroupOverageService,此方法可行但感觉不够规范,希望得到改进建议。
public class GroupOverageMiddleware(RequestDelegate next) { public async Task InvokeAsync(HttpContext context, IGroupOverageService groupOverageService) { await groupOverageService.ProcessAnyGroupOverage(); await next(context); } }
解决方案与建议
1. 直接从TokenValidatedContext获取Token(最优解)
在TokenValidated事件中,TokenValidatedContext本身就包含已验证的JWT令牌,完全不需要通过HttpContext.GetTokenAsync获取,直接从context.SecurityToken提取原始Token字符串即可,从根源上避免递归:
修改TokenValidated方法,将Token传递给服务:
public override async Task TokenValidated(TokenValidatedContext context) { if (context != null && context.SecurityToken is JwtSecurityToken jwtToken) { // 直接从上下文获取原始Token字符串 var accessToken = jwtToken.RawData; await groupOverageService.ProcessAnyGroupOverage(context, accessToken); } await Task.CompletedTask; }
同步调整ProcessAnyGroupOverage方法,直接接收Token参数,不再调用GetAccessToken:
public async Task ProcessAnyGroupOverage(TokenValidatedContext context, string accessToken) { // 使用accessToken调用Microsoft Graph获取组信息 // ... 你的业务逻辑 }
2. 优化自定义中间件方案
如果坚持使用中间件,需调整中间件的注册顺序,确保在认证中间件之后执行,保证用户已完成认证:
var app = builder.Build(); // ... 其他中间件 app.UseAuthentication(); app.UseAuthorization(); // 放在认证之后、业务路由之前 app.UseMiddleware<GroupOverageMiddleware>(); app.MapControllers(); app.Run();
另外,可改用客户端凭据流调用Microsoft Graph:服务端以应用身份请求Graph API查询用户组信息,无需依赖用户的Access Token,既避免递归,也能避免用户令牌权限不足的问题。
3. 结合Microsoft Graph SDK简化实现
如果使用Microsoft Graph SDK,可直接利用TokenValidatedContext中的凭据初始化客户端,无需手动处理Token:
public override async Task TokenValidated(TokenValidatedContext context) { if (context != null && context.SecurityToken is JwtSecurityToken jwtToken) { var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken.RawData); })); // 查询用户所属组 var groups = await graphClient.Me.MemberOf.Request().GetAsync(); // 处理组超限逻辑 // ... } }
总结
最直接的解决方案是从TokenValidatedContext中直接提取原始Token,彻底避免触发递归。如果选择中间件方案,注意调整执行顺序,或改用客户端凭据流提升安全性与稳定性。
内容的提问来源于stack exchange,提问作者Samofan99

