. net Core 3.1 аутентификация с Jwt с одним контекстом - PullRequest
0 голосов
/ 16 февраля 2020

До. NET Core 2.2 без проблем: я создал леса EF Core, в моем Web API был 1 контекст, а в Authenticate методе, который я написал, например,

var user = _context.Users.SingleOrDefault(x => x.UserName == username);

В Чтобы найти пользователя в пользовательской таблице (AspNetUsers), пароль был проверен путем проверки user.PassWordHash и user.PasswordSalt с параметром пароля.

С 3.1 У меня есть работающий веб-API, использующий 2 контексты: 1, полученные с помощью скаффолдинга, как и раньше, который вызывает скаффолдинг таблиц аутентификации (AspNetRoleClaims, AspNetRoles ...), но я не смог использовать эти таблицы, как раньше в моих UsersControllers, были различные ошибки.

Поэтому я вручную создал ApplicationDbContext:

using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace SampleWebApi.Dtos
{
    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
    {
        public ApplicationDbContext(
            DbContextOptions options,
            IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
        {
        }
    }
}

Тогда в моем Startup.cs у меня есть

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<coreusersContext>(options =>
        options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")
    ), ServiceLifetime.Transient);

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")
    ), ServiceLifetime.Transient);

    services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddCors(options =>
    {
        options.AddPolicy("AllowAll",
        builder =>
        {
            builder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
        });
    });

    services.AddAutoMapper(typeof(Startup));
    var appSettingsSection = Configuration.GetSection("AppSettings");
    services.Configure<AppSettings>(appSettingsSection);
    //configure jwt authentication
    var appSettings = appSettingsSection.Get<AppSettings>();
    var key = Encoding.ASCII.GetBytes(appSettings.Secret);
    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
                // not parseInt, Id no nore an Int but a Guid
                var userId = context.Principal.Identity.Name;
                var user = userService.GetByIdAsync(userId);
                if (user == null)
                {
                    // return unauthorized if user no longer exists
                    context.Fail("Unauthorized");
                }
                return Task.CompletedTask;
            }
        };
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

    services.AddMvc().AddJsonOptions(options =>
    {
        // Use the default property (Pascal) casing.
        options.JsonSerializerOptions.PropertyNamingPolicy = null;
        options.JsonSerializerOptions.IgnoreNullValues = true;
    }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

    //configure DI for application services
    services.AddScoped<IUserService, UserService>();
    services.AddScoped<ICompanyService, CompanyService>();
    services.AddScoped<IRepository, RepositoryService>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors("AllowAll");
    app.UseAuthentication();
    app.UseAuthorization(); 
    //
    app.UseEndpoints(endpoints => {
        endpoints.MapControllers();
    });
}

Из приведенного выше кода исключены некоторые мелочи как CORS и Политики. В конце UsersController теперь у меня есть:

...
SignInResult result = await _signInManager.PasswordSignInAsync(username, password, rememberme, lockoutOnFailure: false);

if (result.Succeeded)
{
    var user = await _userManager.FindByNameAsync(username);
    return user;
}

if (result.RequiresTwoFactor)
{
    throw new Exception("auth-requirestwofactor");
}

if (result.IsLockedOut)
{
    throw new Exception("auth-lockout");
}
...

_signInManager - это внедренный экземпляр SignInManager<ApplicationUser>, ApplicationUser определяется как

using Microsoft.AspNetCore.Identity;

namespace SampleWebApi.Dtos
{
    public class ApplicationUser : IdentityUser
    {
    }
}

UserManager это экземпляр UserManager<ApplicationUser>.

При использовании приведенного выше кода аутентификация работает отлично: в UsersController я использую только ApplicationDbContext; для других таблиц базы данных я использую в других контроллерах контекст, сгенерированный из скаффолдинга EF Core.

Так что все работает, но я сомневаюсь, что этот подход действительно правильный.

Если Я закомментирую в Startup.cs, ConfigureServices:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")
), ServiceLifetime.Transient);

явно бесполезно, я получаю ошибку:

Некоторые службы не могут быть построены

...