自定义授权过滤器在 ASP.NET Core 3 中不起作用
Posted
技术标签:
【中文标题】自定义授权过滤器在 ASP.NET Core 3 中不起作用【英文标题】:Custom authorization filter not working in ASP.NET Core 3 【发布时间】:2020-09-22 21:06:48 【问题描述】:我正在使用自定义授权属性筛选器向 ASP.NET Core 3.1 应用程序添加 AzureAD 身份验证(以及最终授权)。下面的代码实现了IAuthorizationFilter
的OnAuthorization
方法,当他们的身份验证过期时,我将用户重定向到SignIn
页面。
当带有[CustomAuthorizationFilter]
的控制器操作被点击时,我希望属性的OnAuthorization
方法会立即被点击,无论身份验证cookie 是否已过期。
这种期望不会发生,相反,如果用户未通过身份验证并且触发了控制器操作,则会自动通过 Microsoft 重新验证用户并创建有效的 cookie,然后才会触发 OnAuthorization
方法,从而击败我认为这是OnAuthorization
方法的目的。
我一直在做很多研究来了解这种行为,但我显然遗漏了一些东西。我发现的最有用的信息在Microsoft docs:
从 ASP.NET Core 3.0 开始,MVC 不会为 在控制器上发现的 [AllowAnonymous] 属性和 行动方法。此更改针对以下衍生产品在本地解决 AuthorizeAttribute,但对于 IAsyncAuthorizationFilter 和 IAuthorizationFilter 实现。
所以,IAuthorizationFilter
的实现似乎在 3.0+ 中被破坏了,我不知道如何修复它。
这种行为是正常的还是我的实现不正确?
如果正常,为什么我在OnAuthorization
方法运行之前要重新认证?
如果不正确,如何正确实现?
CustomAuthorizationFilter.cs
public class CustomAuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter
public void OnAuthorization(AuthorizationFilterContext context)
string signInPageUrl = "/UserAccess/SignIn";
if (context.HttpContext.User.Identity.IsAuthenticated == false)
if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = 401;
JsonResult jsonResult = new JsonResult(new redirectUrl = signInPageUrl );
context.Result = jsonResult;
else
context.Result = new RedirectResult(signInPageUrl);
使用的 IsAjaxRequest() 扩展:
//Needed code equivalent of Request.IsAjaxRequest().
//Found this solution for ASP.NET Core: https://***.com/questions/29282190/where-is-request-isajaxrequest-in-asp-net-core-mvc
//This is the one used in ASP.NET MVC 5: https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/AjaxRequestExtensions.cs
public static class AjaxRequestExtensions
public static bool IsAjaxRequest(this HttpRequest request)
if (request == null)
throw new ArgumentNullException("request");
if (request.Headers != null)
return (request.Headers["X-Requested-With"] == "XMLHttpRequest");
return false;
Startup.cs 中的 AzureAD 身份验证实现
public void ConfigureServices(IServiceCollection services)
IAppSettings appSettings = new AppSettings();
Configuration.Bind("AppSettings", appSettings);
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
options.Instance = appSettings.Authentication.Instance;
options.Domain = appSettings.Authentication.Domain;
options.TenantId = appSettings.Authentication.TenantId;
options.ClientId = appSettings.Authentication.ClientId;
options.CallbackPath = appSettings.Authentication.CallbackPath;
);
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
options.UseTokenLifetime = false;
options.Authority = options.Authority + "/v2.0/"; //Microsoft identity platform
options.TokenValidationParameters.ValidateIssuer = true;
// https://***.com/questions/49469979/azure-ad-b2c-user-identity-name-is-null-but-user-identity-m-instance-claims9
// https://***.com/questions/54444747/user-identity-name-is-null-after-federated-azure-ad-login-with-aspnetcore-2-2
options.TokenValidationParameters.NameClaimType = "name";
//https://***.com/a/53918948/12300287
options.Events.OnSignedOutCallbackRedirect = context =>
context.Response.Redirect("/UserAccess/LogoutSuccess");
context.HandleResponse();
return Task.CompletedTask;
;
);
services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, options =>
options.AccessDeniedPath = "/UserAccess/NotAuthorized";
options.LogoutPath = "/UserAccess/Logout";
options.ExpireTimeSpan = TimeSpan.FromMinutes(appSettings.Authentication.TimeoutInMinutes);
options.SlidingExpiration = true;
);
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication(); // who are you?
app.UseAuthorization(); // are you allowed?
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "controller=UserAccess/action=Login/id?");
);
【问题讨论】:
我也遇到了同样的问题。 @GabrielCarvalho,不知道你是否已经想通了,但我发布了一个我确定的解决方案。 【参考方案1】:我希望找到一种方法来创建一个AuthorizeAttribute
过滤器来解决这个问题,但由于时间限制,我选择了一个常规操作过滤器。它适用于 AJAX 调用,如果用户未经授权或未经身份验证,它会将用户重定向到相应的页面:
AjaxAuthorize 动作过滤器:
//custom AjaxAuthorize filter inherits from ActionFilterAttribute because there is an issue with
//a inheriting from AuthorizeAttribute.
//post about issue:
//https://***.com/questions/64017688/custom-authorization-filter-not-working-in-asp-net-core-3
//The statuses for AJAX calls are handled in InitializeGlobalAjaxEventHandlers JS function.
//While this filter was made to be used on actions that are called by AJAX, it can also handle
//authorization not called through AJAX.
//When using this filter always place it above any others as it is not guaranteed to run first.
//usage: [AjaxAuthorize(new[] "RoleName", "AnotherRoleName")]
public class AjaxAuthorize : ActionFilterAttribute
public string[] Roles get; set;
public AjaxAuthorize(params string[] roles)
Roles = roles;
public override void OnActionExecuting(ActionExecutingContext context)
string signInPageUrl = "/UserAccess/SignIn";
string notAuthorizedUrl = "/UserAccess/NotAuthorized";
if (context.HttpContext.User.Identity.IsAuthenticated)
if (Roles.Length > 0)
bool userHasRole = false;
foreach (var item in Roles)
if (context.HttpContext.User.IsInRole(item))
userHasRole = true;
if (userHasRole == false)
if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = 401;
JsonResult jsonResult = new JsonResult(new redirectUrl = notAuthorizedUrl );
context.Result = jsonResult;
else
context.Result = new RedirectResult(notAuthorizedUrl);
else
if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = 403;
JsonResult jsonResult = new JsonResult(new redirectUrl = signInPageUrl );
context.Result = jsonResult;
else
context.Result = new RedirectResult(signInPageUrl);
使用的 IsAjaxRequest() 扩展(重新发布以获得完整答案):
//Needed code equivalent of Request.IsAjaxRequest().
//Found this solution for ASP.NET Core: https://***.com/questions/29282190/where-is-request-isajaxrequest-in-asp-net-core-mvc
//This is the one used in ASP.NET MVC 5: https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/AjaxRequestExtensions.cs
public static class AjaxRequestExtensions
public static bool IsAjaxRequest(this HttpRequest request)
if (request == null)
throw new ArgumentNullException("request");
if (request.Headers != null)
return (request.Headers["X-Requested-With"] == "XMLHttpRequest");
return false;
javascript ajax 全局错误处理程序:
//global settings for the AJAX error handler. All AJAX error events are routed to this function.
function InitializeGlobalAjaxEventHandlers()
$(document).ajaxError(function (event, xhr, ajaxSettings, thrownError)
//these statuses are set in the [AjaxAuthorize] action filter
if (xhr.status == 401 || xhr.status == 403)
var response = $.parseJSON(xhr.responseText);
window.location.replace(response.redirectUrl);
else
RedirectUserToErrorPage();
);
【讨论】:
这也有助于我的 IAuthorizationFilter 属性基于类的授权。谢谢你的回答 但是我不得不做一些改变来匹配我的场景。以上是关于自定义授权过滤器在 ASP.NET Core 3 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章
拦截asp.net core Authorize action,授权成功后执行自定义动作
如何使用 AuthorizationHandlerContext 在 ASP.NET Core 2 自定义基于策略的授权中访问当前的 HttpContext
ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
ASP.NET Core MVC 授权的扩展:自定义 Authorize Attribute 和 IApplicationModelProvide