.Net Core 2.0 Web API с использованием JWT - Добавление идентификатора нарушает аутентификацию JWT
(Правка - Найдено правильное исправление! См. Ниже)
ОК - это моя первая попытка .Net Core 2.0 и аутентификации, хотя в прошлом я работал с Web API 2.0 и довольно много работал над различными проектами MVC и Webforms ASP в течение последних нескольких лет.
Я пытаюсь создать проект ТОЛЬКО через Web API с использованием .Net Core. Это создаст серверную часть мультитенантного приложения для создания некоторых отчетов, поэтому мне нужно иметь возможность аутентифицировать пользователей. Кажется, что обычный подход заключается в использовании JWT - сначала аутентифицируйте пользователя для генерации токена, а затем передайте его клиенту для использования при каждом запросе API. Данные будут храниться и извлекаться с использованием EF Core.
Я проследовал за этим постом для базового способа настройки, и мне удалось заставить его работать нормально - у меня есть контроллер, который принимает имя пользователя/пароль и возвращает токен, если он действителен, и некоторые политики авторизации, настроенные на основе претензии.
Следующее, что мне нужно, это на самом деле управлять пользователями/паролями/и т.д. Я подумал, что для этого просто буду использовать .Net Core Identity, так как таким образом у меня будет много готового кода для беспокойства о пользователях/ролях, паролях и т.д. Я использовал User
классы User
и UserRole
которые получены из стандартного IdentityUser
и классы IdentityRole
, но с тех пор я вернулся к стандартным.
У меня проблема в том, что я не могу понять, как добавить личность и зарегистрировать все различные сервисы (ролевой менеджер, пользовательский менеджер и т.д.), Не нарушая при этом аутентификацию - в основном, как только я добавлю эту строку в мой класс Startup.ConfigureServices
:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<MyContext>();
Все идет не так, и я больше не вижу претензий при получении запроса, поэтому все политики просто блокируются, и вы ничего не можете получить.
Если у меня нет этих строк, я получаю ошибки, связанные с UserManager, RoleManager, UserStore и т.д., Которые не зарегистрированы для DI.
Итак... как (если это возможно) я могу зарегистрировать Идентичность и правильно подключить ее к Контексту, но избежать/удалить какие-либо изменения в действующем механизме авторизации?
Я довольно много смотрел в Интернете, но многое изменилось со времени .Net Core 1.x, поэтому многие учебные пособия и т.д. Больше не действуют.
Я не собираюсь использовать в этом API-интерфейсе какой-либо интерфейсный код, поэтому мне пока не нужна проверка подлинности с помощью cookie для форм или чего-либо еще.
редактировать
Хорошо, теперь я обнаружил, что в этом коде настройка аутентификации JWT в методе Startup.ConfigureServices()
:
services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
>>breakpoint>>> options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "Blah.Blah.Bearer",
ValidAudience = "Blah.Blah.Bearer",
IssuerSigningKey =
JwtSecurityKey.Create("verylongsecretkey")
};
});
Если я поставлю точку останова в указанной строке (через ">> точку останова >>>"), то получит удар, когда я не добавлю строки для добавления служб идентификации, но если я добавлю эти строки, то она никогда не попадет. Это верно независимо от того, где в методе я помещаю вызов services.AddIdentity()
. Я понял, что это просто лямбда, поэтому он выполняется позже, но есть ли способ, как я могу заставить вещи AddIdentity НЕ устанавливать аутентификацию или заставить код немедленно удалить ее? Я предполагаю, что в какой-то момент есть некоторый код, который выбирает не запускать Lambda для конфигурации, которую я там установил, поскольку Identity уже установил его...
Спасибо, что прочитали все это, если у вас есть :)
РЕДАКТИРОВАТЬ - нашел ответ
Хорошо, в конце концов я нашел эту проблему GH, которая в основном именно эта проблема: https://github.com/aspnet/Identity/issues/1376
В основном то, что я должен был сделать, было двойным:
Убедитесь, что сначала был выполнен вызов services.AddIdentity<IdentityUser, IdentityContext()
Измените вызов, чтобы добавить аутентификацию из:
services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
...
Для того, чтобы:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
...
Это досадно приводит к созданию файла cookie, но, насколько я могу судить, он не используется для аутентификации - он просто использует токен-носитель для запросов к контроллерам/действиям, которые имеют [Authorize(Policy = "Administrator")]
или подобный набор по крайней мере.
Мне нужно больше тестировать, и я постараюсь вернуться сюда с обновлением, если я обнаружу, что это не работает каким-то образом.
(Отредактировано - поставить правильное решение в качестве ответа сейчас)
Ответы
Ответ 1
В конце концов я собрал решение, поэтому по предложению пользователя, которое всегда изучало, я редактировал свой пост, и я помещаю его в качестве фактического ответа.
ok, это можно сделать правильно. Во-первых, вам нужно использовать параметры проверки подлинности, которые я указал в моем правлении выше, - это прекрасно.
Затем вам нужно использовать services.AddIdentityCore<TUser>()
, а не services.AddIdentity<TUser>()
. Это, однако, не добавляет целую кучу вещей для управления ролями и, по-видимому, не имеет надлежащего конструктора, чтобы придать ему тип роли, которую вы хотите использовать. Это означает, что в моем случае я должен был сделать это:
IdentityBuilder builder = services.AddIdentityCore<IdentityUser>(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
}
);
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder
.AddEntityFrameworkStores<MyContext>();
//.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<IdentityRole>>();
builder.AddRoleManager<RoleManager<IdentityRole>>();
builder.AddSignInManager<SignInManager<IdentityUser>>();
Сделав это, нужно убедиться, что при проверке входа пользователя (перед отправкой токена) вы должны использовать метод SignInManager CheckPasswordSignInAsync
, а не PasswordSignInAsync
:
public async Task<IdentityUser> GetUserForLogin(string userName, string password)
{
//find user first...
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return null;
}
//validate password...
var signInResult = await _signInManager.CheckPasswordSignInAsync(user, password, false);
//if password was ok, return this user.
if (signInResult.Succeeded)
{
return user;
}
return null;
}
если вы используете метод PasswordSignInAsync
, тогда вы получите ошибку времени выполнения. Не настроен IAuthenticationSignInHandler.
Я надеюсь, что это поможет кому-то в какой-то момент.
Ответ 2
Я извлек код AddIdentity
из github и создал на его основе метод расширения, который не добавляет по умолчанию Cookie Authenticator. Он теперь очень похож на встроенный AddIdentityCore
но может принимать IdentityRole
.
/// <summary>
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring identity services.
/// </summary>
public static class IdentityServiceExtensions
{
/// <summary>
/// Adds the default identity system configuration for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services)
where TUser : class
where TRole : class
=> services.AddIdentityWithoutAuthenticator<TUser, TRole>(setupAction: null);
/// <summary>
/// Adds and configures the identity system for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="IdentityOptions"/>.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction)
where TUser : class
where TRole : class
{
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
}
Теперь вы можете использовать приведенный выше код, как обычно, из проекта WebApi
.AddIdentityWithoutAuthenticator<User, IdentityRole>()