ASP.NET Core 2.2:配置了多个身份验证方案时,ChallengeResult的预期行为是什么?

时间:2019-04-10 11:52:47

标签: c# authentication asp.net-core .net-core authorization

我们正在尝试了解在注册了多个身份验证方案时对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";
    }
  }

我们注意到:

  • 同时调用FooAuthenticationHandlerBarAuthenticationHandler来处理ChallengeResult
  • 顺序是FooAuthenticationHandler之前的BarAuthenticationHandler,并取决于Authorize属性(如果在Authorize属性内交换身份验证方案,则会调用BarAuthenticationHandler首先)
  • 呼叫者获得原始的500状态代码响应,但这仅取决于调用授权处理程序的顺序
  • 只有在options.DefaultChallengeScheme = "Bar";属性中[Authorize]属性中未设置的情况下,对AuthenticationSchemes的调用很重要。如果这样做,则仅调用BarAuthenticationHandler,而FooAuthenticationHandler将永远没有机会对请求进行身份验证或处理身份验证质询。

因此,基本上的问题是:当您遇到这种情况时,由于挑战结果被同时两者被调用,您应该如何应对与ChallengeResult处理有关的不同身份验证方案可能的“不兼容”?

我们认为双方都有机会对请求进行身份验证是很好的,但是我们想知道是否可以决定由哪个人来处理身份验证挑战。

感谢您的帮助!

1 个答案:

答案 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身份验证。

此处的示例转发了您可以通过身份验证执行的所有“操作”(挑战,禁止等)。 您还可以为挑战等设置前进选择器。