EF Core 2.2 - авторизация на основе ролей не может получить роль моего пользователя - PullRequest
0 голосов
/ 10 июля 2019

Я уже некоторое время стучу головой. Я установил Identity, сопоставленный с MySQL DB для моего API.

Я загружаю свои роли и одного пользователя при запуске:

        if (!_roleManager.RoleExistsAsync("Admin").Result)
        {
            Role role = new Role();
            role.Name = "Admin";
            IdentityResult roleResult = _roleManager.CreateAsync(role).Result;
        }

        if (_userManager.FindByNameAsync("myuser").Result == null)
        {
            User user = new User();
            user.UserName = "myuser";
            user.Email = "myuser@test.com";
            // Not secure, to be moved
            user.PasswordHash = SecurePasswordHasher.Hash("testtest");

            IdentityResult result = _userManager.CreateAsync(user).Result;

            if (result.Succeeded)
            {
                _userManager.AddToRoleAsync(user, "Admin").Wait();
            }
        }

Таблицы Aspnetroles, Aspnetusers и Aspnetuserroles заполнены правильно. При входе в систему получите свой токен на предъявителя и попробуйте получить доступ к конечной точке API, например:

    [HttpGet]
    [Authorize]
    public async Task<IActionResult> Index()
    {
        // Get objects
        var objects = _repository.FindAll();
        // Return response
        return Ok(objects);
    }

Работает нормально. Без токена я получаю 401 Несанкционированный. Все нормально. Но когда я пытаюсь получить доступ к конечной точке, как эта:

    [HttpGet]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> Index()
    {
        // Get objects
        var objects = _repository.FindAll();
        // Return response
        return Ok(objects);
    }

Это не работает, я получаю 403 Запрещено. Что бы я хотел, если бы у меня была неправильная роль, но это не так. Если я назначу роль null вместо "Admin", то это сработает! Поэтому я предполагаю, что что-то не так, и приложение не может получить роль моего пользователя.

DbContext:

public class AppContext : IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
{
        private Settings Settings { get; }

        // Constructor
        public AppContext()
        {
            // Build the MySql settings:
            Settings = new Settings("dev")
                .Configure(new MysqlSettings())
                .Build();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if(!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseLazyLoadingProxies();
                optionsBuilder.UseMySql(Settings.GetRequiredConf<MysqlSettings>().Connection +
                                        "TreatTinyAsBoolean=false;");
            }
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<Role>().ToTable("role");
            builder.Entity<User>().ToTable("user");
            builder.Entity<UserClaim>().ToTable("user_claim");
            builder.Entity<UserToken>().ToTable("user_token");
            builder.Entity<UserLogin>().ToTable("user_login");
            builder.Entity<RoleClaim>().ToTable("role_claim");

            builder.Entity<UserRole>(entity =>
            {
                entity.ToTable("user_role");

                entity.HasOne(d => d.User)
                    .WithOne(p => (Data.Models.Identity.UserRole)p.UserRole)
                    .HasForeignKey<UserRole>(d => d.UserId)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("FK_user_role_user_UserId");

                entity.HasOne(d => d.Role)
                    .WithOne(p => (Data.Models.Identity.UserRole)p.UserRole)
                    .HasForeignKey<UserRole>(d => d.RoleId)
                    .OnDelete(DeleteBehavior.Cascade)
                    .HasConstraintName("FK_user_role_role_RoleId");
            });
        }
}

Запуск:

public void ConfigureServices(IServiceCollection services)
{
        // Add the environment to the services
        services.AddSingleton(Environment);
        // Add the settings to the services
        services.AddSingleton(Settings);
        // Add repository manager
        services.AddRepositoryManager();
        // Add Db context
        services.AddSingleton(Context);
        // Mvc service
        services.AddMvc(options =>
            {
                options.Filters.Add(new AuthorizeFilter());
            })
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        // Identity
        services.AddIdentity<User, Role>(options =>
            {
                options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
                options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
                options.Password.RequireDigit = false;
                options.Password.RequiredLength = 8;
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
            })
            .AddEntityFrameworkStores<BreathBalanzContext>()
            .AddDefaultTokenProviders();

        // Custom password hasher
        services.AddScoped<IPasswordHasher<User>, CustomPasswordHasher>();
        // Authentication service
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(o =>
        {
            o.Authority = Settings.GetRequiredConf<AuthSettings>().Address;
            o.Audience = "Api";
            o.RequireHttpsMetadata = false;
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<User> userManager, RoleManager<Role> roleManager)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAuthentication();

        // Seeding the roles and users here
        Seeder.SeedData(userManager, roleManager);

        app.UseMvc();
    }

У меня действительно заканчиваются идеи. Первоначально отложенная загрузка не была настроена, я думал, что это было причиной проблемы, но это не помогло (хотя отложенная загрузка работает правильно сейчас). Я не уверен, что я пропустил.

EDIT:

Простое _context.Set<User>().Find(id) возвращает следующее, что должно доказать, что Lazy Loading работает и, следовательно, должно быть достаточно для Identity, чтобы получить роль моего пользователя?

{
    "userClaim": [],
    "userToken": [],
    "userLogin": [],
    "userRole": [
        {
            "role": {
                "userRole": [],
                "roleClaim": [],
                "id": 3,
                "name": "Admin",
                "normalizedName": "ADMIN",
                "concurrencyStamp": "34d3e595-035c-4f4b-9e5e-99c8f4221701"
            },
            "userId": 11,
            "roleId": 3
        }
    ],
    "id": 11,
    "userName": "myuser",
    "normalizedUserName": "MYUSER",
    "email": "myuser@dtest.com",
    "normalizedEmail": "MYUSER@TEST.COM",
    "emailConfirmed": false,
    "passwordHash": "$MYHASH$V1$10000$v19jvXvMQeU5KtwWg2vs0f3Ou6J8+ANWJaXjPEkM9eEvQ6pm",
    "securityStamp": "LRITM53N625U4SSFQLCPIYN2UFJHMYJP",
    "concurrencyStamp": "1897bc87-fcd0-4759-ad8c-d21210351e04",
    "phoneNumber": null,
    "phoneNumberConfirmed": false,
    "twoFactorEnabled": false,
    "lockoutEnd": null,
    "lockoutEnabled": true,
    "accessFailedCount": 0
}

1 Ответ

0 голосов
/ 11 июля 2019

После многих часов поисков везде я нашел проблему. Мне просто нужно было добавить область действия Roles в мой токен при ее создании.

public override async Task<TokenResponse> GetTokenAsync(HttpClient client, string address)
{
    return await client.RequestPasswordTokenAsync(new PasswordTokenRequest
    {
        Address = address,
        UserName = Username,
        Password = Password,
        GrantType = "password",
        ClientId = ClientId,
        ClientSecret = ClientSecret,
        Scope = "Api roles"
    });
}

И, конечно, разрешить эту область в конфигурации клиента IdentityServer:

new Client
{
    ClientId = "ro.security",
    ClientSecrets = { new Secret(clientSecret.Sha256()) },
    AllowedScopes = { "Api", "introspection", "roles" },
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AllowOfflineAccess = true
}
...