AuthorizationMiddlewareResultHandler未被调用问题求助
尝试实现基于页面的自定义授权失败提示消息,参考Handling the AuthorizationFailureReason相关逻辑,日志显示FailHandler已触发且设置了自定义消息,但系统直接重定向到/Account/AccessDenied,自定义的AuthorizationMiddlewareResultHandler从未执行。
相关代码文件
Startup.cs
using IAuthorizationMiddlewareResultHandler = MyCompany.IAuthorizationMiddlewareResultHandler; public void ConfigureServices(IServiceCollection services) { ... services.AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme; }) .AddWsFederation(options => { options.ClaimsIssuer = claimsIssuerServer; options.Wtrealm = _wtrealm; options.MetadataAddress = _metaAddress; options.Events.OnRedirectToIdentityProvider = Authentication.OnRedirectToIdentityProvider(claimsIssuerServer, identityProviderServer); options.Events.OnSecurityTokenReceived = Authentication.OnSecurityTokenReceived(_audience, validIssuerServer, signingKey); options.TokenValidationParameters = new TokenValidationParameters { ValidAudience = _audience, ValidateIssuer = true, ValidIssuer = validIssuerServer, ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, }; options.RequireHttpsMetadata = true; }).AddCookie(cookieoption => { cookieoption.Cookie.SecurePolicy = CookieSecurePolicy.Always; cookieoption.EventsType = typeof(CustomCookieAuthenticationEvents); }); services.AddScoped<CustomCookieAuthenticationEvents>(); services.AddRazorPages(options => { ... }) .AddMvcOptions(o => o.Filters.Add(new AuthorizeFilter())); services.AddAuthorization(options => { ... options.AddPolicy("AuthorizedUser", policy => { policy.Requirements.Add(new FailRequirement("AuthorizedUser")); }); }); ... services.AddTransient<IAuthorizationHandler, FailHandler>(); services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>(); services.AddScoped<IClaimsTransformation, AddRolesClaimsTransformation>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... app.UseAuthentication(); app.UseAuthorization(); ... app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); }
Create.cshtml.cs
[Authorize("AuthorizedUser")] public class CreateModel : PageModel { }
AuthorizationMiddlewareResultHandler.cs
public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { private readonly ILogger<AuthorizationMiddlewareResultHandler> _logger; private readonly Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler _defaultHandler = new(); public AuthorizationMiddlewareResultHandler(ILogger<AuthorizationMiddlewareResultHandler> logger) { _logger = logger; } public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { var authorizationFailureReason = authorizeResult.AuthorizationFailure?.FailureReasons.FirstOrDefault(); var message = authorizationFailureReason?.Message; _logger.LogInformation("Authorization Result says {Message}", message ); if (authorizationFailureReason?.Handler is FailHandler) { var html = $"<html><h1>{message}</h1></html>"; context.Response.StatusCode = StatusCodes.Status403Forbidden; await Results.Content(html, "text/html").ExecuteAsync(context); } else { // Fall back to the default implementation. await _defaultHandler.HandleAsync(next, context, policy, authorizeResult); } } }
FailHandler.cs
public class FailHandler : AuthorizationHandler<FailRequirement> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, FailRequirement requirement) { string permission = requirement.RequiredPermission.ToString() ?? ""; string authorizationFailureMessage = "You are not authorized for this page."; if (!context.User.HasClaim(c => c.Type == "Permission" && c.Value == permission)) { switch (permission) { case "AuthorizedUser": authorizationFailureMessage = "You are not eligible for this service at this time."; break; } context.Fail(new AuthorizationFailureReason(this, authorizationFailureMessage)); } else { context.Succeed(requirement); } return Task.CompletedTask; } }
FailRequirement.cs
public class FailRequirement : IAuthorizationRequirement { public FailRequirement(string requiredPermission) { RequiredPermission = requiredPermission; } public string RequiredPermission { get; set; } }
IAuthorizationMiddlewareResultHandler.cs
public interface IAuthorizationMiddlewareResultHandler { Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult); }
日志输出
{"@t":"2023-01-26T19:04:15.7618707Z","@mt":"Authorization failed. {Reason}","Reason":"Fail() was explicitly called.","EventId":{"Id":2,"Name":"UserAuthorizationFailed"},"SourceContext":"Microsoft.AspNetCore.Authorization.DefaultAuthorizationService","RequestId":"80000044-0005-fe00-b63f-84710c7967bb","RequestPath":"/internship/create","ApplicationName":"MyApp","Environment":"Development"}
{"@t":"2023-01-26T19:04:15.7653501Z","@mt":"AuthenticationScheme: {AuthenticationScheme} was forbidden.","AuthenticationScheme":"Cookies","EventId":{"Id":13,"Name":"AuthenticationSchemeForbidden"},"SourceContext":"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler","RequestId":"80000044-0005-fe00-b63f-84710c7967bb","RequestPath":"/internship/create","ApplicationName":"MyApp","Environment":"Development"}
{"@t":"2023-01-26T19:04:15.7654538Z","@mt":"AuthenticationScheme: {AuthenticationScheme} was forbidden.","AuthenticationScheme":"WsFederation","EventId":{"Id":13,"Name":"AuthenticationSchemeForbidden"},"SourceContext":"Microsoft.AspNetCore.Authentication.WsFederation.WsFederationHandler","RequestId":"80000044-0005-fe00-b63f-84710c7967bb","RequestPath":"/internship/create","ApplicationName":"MyApp","Environment":"Development"}
{"@t":"2023-01-26T19:04:19.9125753Z","@mt":"{HostingRequestFinishedLog:l}","@r":["Request finished HTTP/2 GET https://localhost:44386/internship/create - - - 302 - - 9840.7917ms"],"ElapsedMilliseconds":9840.7917,"StatusCode":302,"ContentType":null,"ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:44386","PathBase":"","Path":"/internship/create","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:44386/internship/create - - - 302 - - 9840.7917ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"80000044-0005-fe00-b63f-84710c7967bb","RequestPath":"/internship/create","ApplicationName":"MyApp","Environment":"Development"}
{"@t":"2023-01-26T19:04:19.9188021Z","@mt":"{HostingRequestStartingLog:l}","@r":["Request starting HTTP/2 GET https://localhost:44386/Account/AccessDenied?ReturnUrl=%2Finternship%2Fcreate - -"],"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:44386","PathBase":"","Path":"/Account/AccessDenied","QueryString":"?ReturnUrl=%2Finternship%2Fcreate","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:44386/Account/AccessDenied?ReturnUrl=%2Finternship%2Fcreate - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"80000046-0005-fe00-b63f-84710c7967bb","RequestPath":"/Account/AccessDenied","ApplicationName":"MyApp","Environment":"Development"}
问题原因与解决方案
核心原因
自定义的IAuthorizationMiddlewareResultHandler未被框架正确识别,因为你创建了同名的自定义接口MyCompany.IAuthorizationMiddlewareResultHandler,而ASP.NET Core框架依赖的是Microsoft.AspNetCore.Authorization.Policy.IAuthorizationMiddlewareResultHandler接口。框架无法注入你的实现,因此始终使用默认处理逻辑,导致授权失败后直接重定向到AccessDenied页面。日志中的"AuthenticationScheme was forbidden"是授权失败后认证中间件的默认行为,同样源于自定义结果处理器未生效。
修复步骤
删除自定义的
IAuthorizationMiddlewareResultHandler接口
移除MyCompany.IAuthorizationMiddlewareResultHandler文件,让自定义处理器直接实现框架提供的接口。更新
AuthorizationMiddlewareResultHandler的接口引用
修改类文件,确保实现框架接口:using Microsoft.AspNetCore.Authorization.Policy; public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { // 现有代码保持不变 }调整Startup.cs中的引用与服务注册
移除自定义接口的别名,使用框架接口引用:// 移除:using IAuthorizationMiddlewareResultHandler = MyCompany.IAuthorizationMiddlewareResultHandler; using Microsoft.AspNetCore.Authorization.Policy; // 服务注册代码保持: services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();可选:禁用认证中间件的默认禁止重定向
若需完全避免重定向行为,可在Cookie认证配置中添加以下逻辑(修复后自定义处理器已接管逻辑,此步骤可选):.AddCookie(cookieoption => { cookieoption.Cookie.SecurePolicy = CookieSecurePolicy.Always; cookieoption.EventsType = typeof(CustomCookieAuthenticationEvents); cookieoption.Events.OnForbidden = context => Task.CompletedTask; });
验证
修复后重启应用,授权失败时AuthorizationMiddlewareResultHandler.HandleAsync会被触发,日志将输出自定义消息,并返回包含提示的403页面,不再重定向到/Account/AccessDenied。
内容的提问来源于stack exchange,提问作者Robert

