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

如何在Action Filter中使用MS Graph检查Azure AD令牌有效性

问题:Action Filter中Azure AD令牌过期无法自动重定向登录

我需要在前端调用控制器时,通过Action Filter检查Azure AD颁发的令牌是否有效。直接在控制器方法中运行代码时:

  • 令牌过期或缓存清空时,MS Graph SDK会自动重定向到微软登录页;
  • 令牌有效时,正常调用MS Graph API并返回结果。

但在Action Filter中遇到令牌过期场景时,代码直接报错,无法自动重定向或向前端返回响应,加了try-catch也没用。

错误信息

{Code: generalException
Message: An error occurred sending the request.
}

{Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. 
 ---> MSAL.NetCore.4.46.0.0.MsalUiRequiredException: 
    ErrorCode: user_null
Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call. 
   at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ApiConfig.Executors.ClientApplicationBaseExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenSilentParameters silentParameters, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForWebAppWithAccountFromCacheAsync(IConfidentialClientApplication application, ClaimsPrincipal claimsPrincipal, IEnumerable`1 scopes, String tenantId, MergedOptions mergedOptions, String userFlow, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
    StatusCode: 0 
    ResponseBody:  
    Headers: 
   --- End of inner exception stack trace ---
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)}

初始Action Filter代码

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Extensions.DependencyInjection;
using System;

public class CheckTokenFilter : IActionFilter
{

private readonly GraphServiceClient _graphServiceClient;
private readonly ITokenAcquisition _tokenAcquisition;
private readonly string[] initialScopes;
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;

public CheckTokenFilter(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition, IConfiguration configuration, MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
{
    _graphServiceClient = graphServiceClient;
    _tokenAcquisition = tokenAcquisition;
    initialScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
    this._consentHandler = consentHandler;
}

public void OnActionExecuted(ActionExecutedContext context)
{
    //noop
}

public async void OnActionExecuting(ActionExecutingContext context)
{
    User currentUser = null;

    try
    {
        currentUser = await _graphServiceClient.Me.Request().GetAsync();
    }
    // Catch CAE exception from Graph SDK
    catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge"))
    {
        try
        {
            Console.WriteLine($"{svcex}");
            string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
            _consentHandler.ChallengeUser(initialScopes, claimChallenge);
        }
        catch (Exception ex2)
        {
            _consentHandler.HandleException(ex2);
        }
    }
}

修正后可行的Action Filter代码

public class CheckTokenFilter : IActionFilter
{

    private readonly GraphServiceClient _graphServiceClient;
    private string SignedInRedirectUri;

    public CheckTokenFilter(GraphServiceClient graphServiceClient, IConfiguration configuration)
    {
        _graphServiceClient = graphServiceClient;
        SignedInRedirectUri = configuration["AzureAd:SignedInRedirectUri"];
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        //noop
    }

    public async void OnActionExecuting(ActionExecutingContext context)
    {
        // 检查令牌是否有效,无效则触发登录重定向,最终向前端返回403并引导至登录页
        User currentUser = null;

        try
        {
            currentUser = await _graphServiceClient.Me.Request().GetAsync();
        }
        // 捕获令牌过期等所有异常
        catch
        {
            context.Result = new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = SignedInRedirectUri
            });
        }
    }
}

问题原因与解决思路

初始代码的问题

  1. 异常捕获范围不足:仅处理了CAE(持续访问评估)相关的ServiceException,未覆盖令牌过期导致的MsalUiRequiredException,大量异常无法被捕获;
  2. 重定向逻辑无效:MicrosoftIdentityConsentAndConditionalAccessHandler的ChallengeUser方法依赖控制器上下文,在Action Filter中无法直接触发重定向响应;
  3. 异步方法错误:使用async void定义OnActionExecuting,异步操作未被正确等待,导致异常处理逻辑无法按预期执行。

修正方案

  1. 扩大异常捕获范围:直接捕获所有异常(或明确捕获MsalUiRequiredException),确保令牌过期场景被覆盖;
  2. 利用ASP.NET Core身份认证系统:通过设置context.Result = new ChallengeResult(),让框架自动处理登录重定向流程,指定认证方案和重定向URI,确保用户登录后回到原请求路径;
  3. 简化依赖:移除不必要的ITokenAcquisition和MicrosoftIdentityConsentAndConditionalAccessHandler依赖,减少组件耦合。

内容的提问来源于stack exchange,提问作者Qiuzman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:30:44