我正在努力使用自定义授权属性过滤器将 AzureAD 身份验证(以及最终授权)添加到 ASP.NET Core 3.1 应用程序。下面的代码实现了IAuthorizationFilter
's OnAuthorization
我将用户重定向到的方法SignIn
身份验证过期时的页面。
当控制器动作时[CustomAuthorizationFilter]
被击中我期望属性的OnAuthorization
无论身份验证 cookie 是否已过期,都会立即调用该方法。
这种期望不会发生,相反,如果用户未经过身份验证并且点击了控制器操作,则系统会自动通过 Microsoft 重新对用户进行身份验证,并创建一个有效的 cookie,只有这样,OnAuthorization
方法被击中,击败了我认为的目的OnAuthorization
method.
我做了很多研究来理解这种行为,但我显然遗漏了一些东西。我发现的最有用的信息是微软文档 https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.0#authorization-iallowanonymous-removed-from-authorizationfiltercontextfilters:
从 ASP.NET Core 3.0 开始,MVC 不添加AllowAnonymousFilters
在控制器上发现的 [AllowAnonymous] 属性和
行动方法。此更改已在本地针对以下衍生品进行解决
AuthorizeAttribute,但这是一个重大变化
IAsyncAuthorizationFilter 和 IAuthorizationFilter 实现。
所以,看来实现与IAuthorizationFilter
在 3.0+ 中可能会被破坏,我不知道如何修复它。
这种行为正常还是我的实施不正确?
如果正常的话为什么我要在之前重新验证OnAuthorization
方法运行?
如果不正确,我该如何正确实施?
自定义授权Filter.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://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 身份验证实现
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?}");
});
}