.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>()