Мы пытаемся понять, какова ожидаемая обработка ChallengeResult, когда зарегистрировано несколько схем аутентификации.
Нам нужно обработать такой сценарий, потому что у нас есть приложение ASP.NET core 2.2, предоставляющее некоторые методы действий (мы используем промежуточное программное обеспечение MVC), которые должны использоваться SPA angularjs, который полагается на аутентификацию файлов cookie, и некоторые сторонние приложения, которые используют аутентификацию. механизм, основанный на заголовке 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
вызываются для обработки ChallengeResultFooAuthenticationHandler
перед BarAuthenticationHandler
и зависит от атрибута Authorize
(если вы поменяете схемы аутентификации внутри атрибута Authorize
, то сначала вызывается BarAuthenticationHandler
)options.DefaultChallengeScheme = "Bar";
имеет значение если и только если внутри атрибута [Authorize]
свойство AuthenticationSchemes
имеет значение нет. Если вы это сделаете, будет вызван только BarAuthenticationHandler
, а FooAuthenticationHandler
никогда не получит шанс аутентифицировать запрос или обработать вызов аутентификации.Итак, вопрос в основном таков: когда у вас есть такой сценарий, как вы должны обрабатывать возможную «несовместимость» различных схем аутентификации в отношении обработки ChallengeResult, поскольку они вызывают обе?
По нашему мнению, это нормально, что у обоих есть шанс аутентифицировать запрос, но мы хотели бы знать, можно ли решить, какой из них должен обрабатывать вызов аутентификации.
Спасибо за помощь !
Не следует указывать схемы в атрибуте 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, если маршрут начинается с /api. Вы можете делать там любые проверки, заголовки и т.д.
Таким образом, в этом случае OpenID Connect и файлы cookie настроены по умолчанию для всего, но если получен вызов, который идет к API, используйте аутентификацию JWT.
В приведенном здесь примере передаются все «действия», которые вы можете выполнить с аутентификацией (запрос, запрет и т. д.). Вы также можете настроить селекторы вперед только для задач и т. д.
Из любопытства, какие две схемы вы хотите поддержать?
В настоящее время приложение написано на .NET 4.7.1 и представляет собой приложение ASP.NET MVC 5. Мы хотим перенести его на ASP.NET core 2.2. Весь механизм аутентификации является пользовательским, у нас в основном есть собственная реализация сервера идентификации. План верхнего уровня для миграции использует схему файлов cookie для клиентского приложения angularjs и пользовательскую схему, основанную на заголовке авторизации HTTP-запроса для сторонних приложений (вызовы сервер-сервер для вызовов клиент-сервер, выполняемые проектами, использующими нашу инфраструктуру). ).
В настоящее время у нас есть несколько контроллеров ASP.NET web api 2, которые используются обе клиентом angularjs и сторонними интеграторами. Текущий (настраиваемый) механизм аутентификации выполняет несколько попыток аутентификации запроса, в основном проверяет наличие файла cookie и заголовка Authorize. Если он находит любой из них, он устанавливает идентификатор для принципала пользователя запроса, и запрос аутентифицируется. Мы пытаемся выяснить, как перенести эту реализацию на ядро ASP.NET.
помимо нашего конкретного производственного сценария, я хотел бы понять, как структура аутентификации ядра ASP.NET была разработана для обработки таких сценариев с несколькими настроенными схемами аутентификации, имеющими различное поведение в случае проблемы аутентификации (я имею в виду, что такое наилучшая практика — каков ожидаемый способ использования предоставленного API для аутентификации?).
Делая так, как вы предлагаете, возможно ли дать обе обработчикам аутентификации возможность выполнить аутентификацию, но принудительно (на основе надлежащего условия) обрабатывать запрос аутентификации только один из них? Если это возможно, мы должны выбрать правильный обработчик для проверки подлинности в зависимости от типа получаемого вызова (если angularjs, то cookie, иначе пользовательский обработчик). Это правильно?