ASP.NET Core 2.0, объединяющий файлы cookie и авторизацию на предъявителя для одной и той же конечной точки
Я создал новый проект ASP.NET Core Web Application в VS17, используя шаблон "Web Application (Model-View-Controller)" и ".Net Framework" + "ASP.NET Core 2" в качестве конфигурации. Конфигурация аутентификации установлена на "Индивидуальные учетные записи пользователей".
У меня есть следующая конечная точка образца:
[Produces("application/json")]
[Route("api/price")]
[Authorize(Roles = "PriceViwer", AuthenticationSchemes = "Cookies,Bearer")]
public class PriceController : Controller
{
public IActionResult Get()
{
return Ok(new Dictionary<string, string> { {"Galleon/Pound",
"999.999" } );
}
}
"Cookies,Bearer"
получают путем объединения CookieAuthenticationDefaults.AuthenticationScheme
и JwtBearerDefaults.AuthenticationScheme
.
Цель состоит в том, чтобы иметь возможность настроить авторизацию для конечной точки так, чтобы она могла получить к ней доступ, используя методы аутентификации как с помощью токена, так и с помощью cookie.
Вот настройки, которые я имею для Аутентификации в моем Startup.cs:
services.AddAuthentication()
.AddCookie(cfg => { cfg.SlidingExpiration = true;})
.AddJwtBearer(cfg => {
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters() {
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
Поэтому, когда я пытаюсь получить доступ к конечной точке с помощью браузера, я получаю ответ 401 с пустой HTML-страницей.
![I get the 401 response with a blank html page.]()
Затем я вхожу в систему, и когда я снова пытаюсь получить доступ к конечной точке, я получаю тот же ответ.
Затем я пытаюсь получить доступ к конечной точке, указав токен на предъявителя. И это возвращает желаемый результат с ответом 200.
![And that returns the desired result with the 200 response.]()
Итак, если я удаляю [Authorize(AuthenticationSchemes = "Cookies,Bearer")]
, ситуация становится противоположной - проверка подлинности cookie работает и возвращает 200, однако тот же метод маркера носителя, который использовался выше, не дает никаких результатов и просто перенаправляет на страницу входа по умолчанию AspIdentity.
Я вижу две возможные проблемы здесь:
1) ASP.NET Core не допускает "комбинированную" аутентификацию. 2) "Cookies" не является допустимым именем схемы. Но тогда что правильно использовать?
Пожалуйста, порекомендуйте. Спасибо.
Ответы
Ответ 1
Я думаю, вам не нужно устанавливать AuthenticationScheme для вашего контроллера. Просто используйте аутентифицированного пользователя в ConfigureServices следующим образом:
// requires: using Microsoft.AspNetCore.Authorization;
// using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Для документации моих источников: registerAuthorizationHandlers
Для части, если схема-ключ был недействительным, вы можете использовать интерполированную строку, чтобы использовать правильные ключи:
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme},{JwtBearerDefaults.AuthenticationScheme}")]
Редактировать: я провел дальнейшие исследования и пришел к следующему выводу: невозможно авторизовать метод с двумя схемами или подобными, но вы можете использовать два открытых метода для вызова частного метода, подобного этому:
//private method
private IActionResult GetThingPrivate()
{
//your Code here
}
//Jwt-Method
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme}")]
[HttpGet("bearer")]
public IActionResult GetByBearer()
{
return GetThingsPrivate();
}
//Cookie-Method
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme}")]
[HttpGet("cookie")]
public IActionResult GetByCookie()
{
return GetThingsPrivate();
}
Ответ 2
Если я правильно понимаю вопрос, то я считаю, что есть решение. В следующем примере я использую проверку подлинности на основе файлов cookie и предъявителя в одном приложении. Атрибут [Authorize]
можно использовать без указания схемы, и приложение будет реагировать динамически, в зависимости от используемого метода авторизации.
services.AddAuthentication
вызывается дважды, чтобы зарегистрировать 2 схемы аутентификации. Ключом к решению является вызов services.AddAuthorization
в конце фрагмента кода, который указывает ASP.NET использовать ОБА схемы.
Я проверил это, и, кажется, работает хорошо.
(На основе документов Microsoft.)
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:4991";
options.RequireHttpsMetadata = false;
options.ClientId = "WebApp";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.Scope.Add("api");
options.SaveTokens = true;
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:4991";
options.RequireHttpsMetadata = false;
// name of the API resource
options.Audience = "api";
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
РЕДАКТИРОВАТЬ
Это работает для аутентифицированных пользователей, но просто возвращает 401 (не авторизованный), если пользователь еще не вошел в систему.
Чтобы гарантировать, что неавторизованные пользователи будут перенаправлены на страницу входа, добавьте следующий код в метод Configure
в своем классе запуска. Примечание: важно, чтобы новое промежуточное ПО было размещено после вызова app.UseAuthentication()
.
app.UseAuthentication();
app.Use(async (context, next) =>
{
await next();
var bearerAuth = context.Request.Headers["Authorization"]
.FirstOrDefault()?.StartsWith("Bearer ") ?? false;
if (context.Response.StatusCode == 401
&& !context.User.Identity.IsAuthenticated
&& !bearerAuth)
{
await context.ChallengeAsync("oidc");
}
});
Если вы знаете более чистый способ добиться этого перенаправления, пожалуйста, оставьте комментарий!