问题描述
我正在使用自定义授权属性筛选器将AzureAD的身份验证(并最终授权)添加到ASP.NET Core 3.1应用程序中.下面的代码实现了 IAuthorizationFilter
的 OnAuthorization
方法,在该方法中,当用户的身份验证过期时,我会将用户重定向到 SignIn
页面.
I'm working on adding authentication (and eventually authorization) with AzureAD to an ASP.NET Core 3.1 app using a custom authorization attribute filter. The code below implements the IAuthorizationFilter
's OnAuthorization
method within which I redirect the user to the SignIn
page when their authentication expires.
当使用 [CustomAuthorizationFilter]
进行控制器操作时,无论验证cookie是否已过期,我都希望立即点击属性的 OnAuthorization
方法.
When a controller action with [CustomAuthorizationFilter]
is hit I expect the attribute's OnAuthorization
method to be hit right away whether or not the authentication cookie has expired.
不会发生这种期望,相反,如果未对用户进行身份验证并单击了控制器操作,则会自动向Microsoft进行用户重新身份验证并创建有效的cookie,然后才会执行 OnAuthorization
方法被击中,打败了我认为 OnAuthorization
方法的目的.
That expectation doesn't happen and instead if a user is not authenticated and a controller action is hit, user is automatically reauthenticated with Microsoft and a valid cookie is created, and only then the OnAuthorization
method is hit, defeating what I thought was the purpose of the OnAuthorization
method.
我已经做了很多研究来了解这种行为,但是我显然缺少了一些东西.我发现的最有用的信息是在 Microsoft文档:
I've been doing a lot of research to understand this behavior, but I'm clearly missing something. The most useful piece of information I found was in Microsoft docs:
因此,看来 IAuthorizationFilter
的实现可能在3.0及更高版本中被破坏,我不知道如何解决.
So, it appears that implementations with IAuthorizationFilter
may be broken in 3.0+ and I don't know how to fix it.
这是正常现象还是我的实现不正确?
Is this behavior normal or is my implementation incorrect?
如果正常,为什么在运行 OnAuthorization
方法之前需要重新认证?
If normal, why am I reauthenticated before the OnAuthorization
method runs?
如果不正确,如何正确实施?
If incorrect, how can I implement it correctly?
CustomAuthorizationFilter.cs
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()扩展名:
The IsAjaxRequest() extension used:
//Needed code equivalent of Request.IsAjaxRequest().
//Found this solution for ASP.NET Core: https://stackoverflow.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身份验证实现
AzureAD authentication implementation in Startup.cs
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://stackoverflow.com/questions/49469979/azure-ad-b2c-user-identity-name-is-null-but-user-identity-m-instance-claims9
// https://stackoverflow.com/questions/54444747/user-identity-name-is-null-after-federated-azure-ad-login-with-aspnetcore-2-2
options.TokenValidationParameters.NameClaimType = "name";
//https://stackoverflow.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?}");
});
}
推荐答案
我希望找到一种创建 AuthorizeAttribute
过滤器的方法来解决此问题,但是由于时间的限制,我选择了常规方法动作过滤器.它可以与AJAX调用一起使用,并且可以将用户重定向到适当的页面(如果这些页面未经授权或未经验证):
I hoped to find a way to create an AuthorizeAttribute
filter to solve this issue, but due to time constraints I settled on a regular action filter. It works with AJAX calls and it redirects the user to the appropriate pages if they are unauthorized or unauthenticated:
AjaxAuthorize操作过滤器:
AjaxAuthorize action filter:
//custom AjaxAuthorize filter inherits from ActionFilterAttribute because there is an issue with
//a inheriting from AuthorizeAttribute.
//post about issue:
//https://stackoverflow.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()扩展名(重新发布以获取完整答案):
The IsAjaxRequest() extension used (reposted for a complete answer):
//Needed code equivalent of Request.IsAjaxRequest().
//Found this solution for ASP.NET Core: https://stackoverflow.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全局错误处理程序:
JavaScript ajax global error handler:
//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();
}
});
}
这篇关于自定义授权筛选器在ASP.NET Core 3中不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!