You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core中避免JwtBearerEvents递归获取Access Token的方法

解决ASP.NET Core API中Azure Entra ID组超限处理的递归问题

场景说明

我正在构建一个采用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:49:51