【发布时间】: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://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?}");
});
}
【问题讨论】:
-
我也遇到了同样的问题。
-
@GabrielCarvalho,不知道你是否已经想通了,但我发布了一个我确定的解决方案。
标签: c# asp.net-mvc asp.net-core authentication asp.net-core-3.1