ASP.NET core 2.2: каково ожидаемое поведение ChallengeResult, когда настроено несколько схем аутентификации? - PullRequest
1 голос
/ 10 апреля 2019

Мы пытаемся понять, какова ожидаемая обработка ChallengeResult, когда зарегистрировано несколько схем аутентификации.

Нам нужно разобраться с таким сценарием, потому что у нас есть приложение ASP.NET core 2.2, предоставляющее некоторые методы действий (мы используем промежуточное программное обеспечение MVC), которые должны использоваться Angularjs SPA, который опирается на проверку подлинности куки-файлов и некоторые сторонние приложения, которые использовать механизм аутентификации на основе заголовка HTTP-запроса авторизации. Обратите внимание, что методы задействованных действий одинаковы для обоих пользователей , это означает, что каждый из них должен разрешать аутентификацию с использованием как файла cookie, так и пользовательской схемы, основанной на заголовке HTTP-запроса авторизации. Мы знаем, что, вероятно, это не оптимальный дизайн, но мы не можем изменить общую архитектуру.

Эта документация , кажется, подтверждает, что то, чего мы хотели бы достичь, вполне возможно с использованием ядра ASP.NET 2.2. К сожалению, аутентификация cookie, используемая приложением пользовательского интерфейса, и пользовательская аутентификация, используемая третьими сторонами, должны вести себя по-разному в случае вызова аутентификации, и их ожидаемое поведение несовместимо друг с другом: приложение пользовательского интерфейса должно перенаправить пользователя в форму входа в систему. в то время как стороннее приложение ожидает необработанный ответ кода состояния 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 вызывается первым)
  • вызывающая сторона получает необработанный ответ с кодом состояния 500, но это зависит только от порядка, в котором называются обработчики авторизации
  • вызов options.DefaultChallengeScheme = "Bar"; имеет значение тогда и только тогда, когда внутри атрибута [Authorize] свойство AuthenticationSchemes установлено , а не установлено. Если вы это сделаете, будет вызван только BarAuthenticationHandler, и FooAuthenticationHandler никогда не получит шанс аутентифицировать запрос или обработать запрос аутентификации.

Итак, вопрос в основном таков: когда у вас есть такой сценарий, как вы должны справиться с возможной «несовместимостью» различных схем аутентификации, связанных с обработкой ChallengeResult, так как они вызывают оба ?

По нашему мнению, хорошо, что у обоих есть возможность аутентифицировать запрос, но мы хотели бы знать, возможно ли решить, какой из них должен обрабатывать запрос аутентификации.

Спасибо за помощь!

1 Ответ

1 голос
/ 10 апреля 2019

Не следует указывать схемы в атрибуте 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;
};

Итак, он выполняет пересылку вызовов (и, конечно же, всего) обработчику JWT, если маршрут начинается с /апи.Там вы можете выполнять любые проверки, заголовки и т. Д.

Так что в этом случае OpenID Connect и Cookies устанавливаются по умолчанию для всех, но если получен вызов, поступающий в API, используйте аутентификацию JWT.

Пример, приведенный здесь, перенаправляет все «действия», которые вы можете выполнить с аутентификацией (вызов, запрет и т. Д.).Вы также можете настроить селекторы вперед только для вызовов и т. Д.

...