如何在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 }); } } }
问题原因与解决思路
初始代码的问题
- 异常捕获范围不足:仅处理了CAE(持续访问评估)相关的
ServiceException,未覆盖令牌过期导致的MsalUiRequiredException,大量异常无法被捕获; - 重定向逻辑无效:
MicrosoftIdentityConsentAndConditionalAccessHandler的ChallengeUser方法依赖控制器上下文,在Action Filter中无法直接触发重定向响应; - 异步方法错误:使用
async void定义OnActionExecuting,异步操作未被正确等待,导致异常处理逻辑无法按预期执行。
修正方案
- 扩大异常捕获范围:直接捕获所有异常(或明确捕获
MsalUiRequiredException),确保令牌过期场景被覆盖; - 利用ASP.NET Core身份认证系统:通过设置
context.Result = new ChallengeResult(),让框架自动处理登录重定向流程,指定认证方案和重定向URI,确保用户登录后回到原请求路径; - 简化依赖:移除不必要的
ITokenAcquisition和MicrosoftIdentityConsentAndConditionalAccessHandler依赖,减少组件耦合。
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

