我们正在尝试了解在注册了多个身份验证方案时对ChallengeResult的预期处理方式。
我们需要处理这种情况,因为我们有一个ASP.NET core 2.2应用程序,该应用程序公开了一些依赖cookie身份验证的angularjs SPA和某些第三方应用程序必须使用的某些操作方法(我们使用MVC中间件)。使用基于Authorization HTTP请求标头的身份验证机制。请注意,这两个用户所涉及的操作方法都是相同的,这意味着它们中的每一个都必须允许使用cookie和基于Authorization HTTP请求标头的自定义方案进行身份验证。我们知道这可能不是最佳设计,但是我们无法修改整体架构。
This documentation似乎证实了我们希望使用ASP.NET core 2.2完全能够实现的目标。不幸的是,在遇到身份验证挑战时,UI应用程序使用的cookie身份验证和第三方使用的自定义身份验证必须行为不同,并且它们的预期行为彼此不兼容:UI应用程序应将用户重定向到登录表单,而第三方应用程序期望原始的401状态代码响应。上面链接的文档没有提供对ChallengeResult处理的清晰说明,因此我们决定尝试测试应用程序。
我们创建了两个伪造的身份验证处理程序:
public class FooAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Foo failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
public class BarAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Bar failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
我们在ConfigureServices方法中注册了身份验证架构,如下所示:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "Bar";
options.AddScheme<FooAuthenticationHandler>("Foo", "Foo scheme");
options.AddScheme<BarAuthenticationHandler>("Bar", "Bar scheme");
});
}
这是我们的中间件管道:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// 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.UseAuthentication();
app.UseMvc();
}
最后,我们创建了一个具有要求认证的操作方法的控制器:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(AuthenticationSchemes = "Foo,Bar")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
我们注意到:
FooAuthenticationHandler
和BarAuthenticationHandler
来处理ChallengeResult FooAuthenticationHandler
之前的BarAuthenticationHandler
,并取决于Authorize
属性(如果在Authorize
属性内交换身份验证方案,则会调用BarAuthenticationHandler
首先)options.DefaultChallengeScheme = "Bar";
属性中[Authorize]
属性中未设置的情况下,对AuthenticationSchemes
的调用很重要。如果这样做,则仅调用BarAuthenticationHandler
,而FooAuthenticationHandler
将永远没有机会对请求进行身份验证或处理身份验证质询。 因此,基本上的问题是:当您遇到这种情况时,由于挑战结果被同时两者被调用,您应该如何应对与ChallengeResult处理有关的不同身份验证方案可能的“不兼容”?
我们认为双方都有机会对请求进行身份验证是很好的,但是我们想知道是否可以决定由哪个人来处理身份验证挑战。
感谢您的帮助!
答案 0 :(得分:1)
您不应在Authorize属性上指定方案。 相反,请指定一种方案作为默认方案,然后设置一个前向选择器。
选择器的实现取决于您的情况,但是通常您可以以某种方式找出请求中使用了哪种方案。
例如,这是来自OpenID Connect方案设置的示例。
o.ForwardDefaultSelector = ctx =>
{
// If the current request is for this app's API
// use JWT Bearer authentication instead
return ctx.Request.Path.StartsWithSegments("/api")
? JwtBearerDefaults.AuthenticationScheme
: null;
};
因此,如果路由以/ api开头,那么它将向JWT处理程序提出挑战(以及所有内容)。 您可以在那里进行任何类型的检查,标题等。
因此,在这种情况下,OpenID Connect和Cookies被设置为所有内容的默认设置,但是如果接收到要访问API的调用,请使用JWT身份验证。
此处的示例转发了您可以通过身份验证执行的所有“操作”(挑战,禁止等)。 您还可以为挑战等设置前进选择器。