ASP.NET Core Identity 2: User.IsInRole всегда возвращает false - PullRequest
0 голосов
/ 13 ноября 2018

Вопрос: Я звоню RoleManager.CreateAsync() и RoleManager.AddClaimAsync(), чтобы создать роли и связанные заявки о ролях.Затем я звоню UserManager.AddToRoleAsync(), чтобы добавить пользователей к этим ролям.Но когда пользователь входит в систему, ни роли, ни связанные утверждения не отображаются в ClaimsPrincipal (то есть в объекте User контроллера).Результатом этого является то, что User.IsInRole() всегда возвращает false, а коллекция утверждений, возвращаемых User.Claims, не содержит утверждений о роли, а аннотации [Authorize(policy: xxx)] не работают.

Я должентакже добавьте, что одним из решений является возврат от использования нового services.AddDefaultIdentity() (который предоставляется шаблонным кодом) обратно к вызову services.AddIdentity().AddSomething().AddSomethingElse().Я не хочу туда идти, потому что я видел слишком много противоречивых историй в Интернете о том, что мне нужно сделать, чтобы настроить AddIdentity для различных вариантов использования.AddDefaultIdentity, кажется, делает большинство вещей правильно без большого количества свободной конфигурации.

Кстати, я задаю этот вопрос с намерением ответить на него ... если кто-то другой не даст мне лучший ответ, чемодин я готов опубликовать.Я также задаю этот вопрос, потому что после нескольких недель поиска мне еще не удалось найти хороший сквозной пример создания и использования ролей и утверждений в ASP.NET Core Identity 2 .Надеемся, что пример кода в этом вопросе может помочь кому-то еще, кто наткнется на него ...

Настройка: Я создал новое веб-приложение ASP.NET Core, выберите Веб-приложение (Модель-View-Controller) и измените Аутентификацию на Индивидуальные учетные записи пользователей.В полученном проекте я делаю следующее:

  • В консоли диспетчера пакетов обновите базу данных, чтобы она соответствовала миграции в леса:

    update-database

  • Добавьте класс ApplicationUser, который расширяет IdentityUser.Это включает добавление класса, добавление строки кода к ApplicationDbContext и замену каждого экземпляра <IdentityUser> на <ApplicationUser> повсюду в проекте.

    Новый ApplicationUser класс:

    public class ApplicationUser : IdentityUser
    {
        public string FullName { get; set; }
    }
    

    Обновленный класс ApplicationDbContext:

    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        { }
    
        // Add this line of code
        public DbSet<ApplicationUser> ApplicationUsers { get; set; }
    }
    
  • В консоли диспетчера пакетов создайте новую миграцию и обновите базу данных, включив в нее сущность ApplicationUsers.

    add -igration m_001
    update-database

  • Добавьте следующую строку кода в Startup.cs, чтобы включить RoleManager

    services.AddDefaultIdentity<ApplicationUser>()
        .AddRoles<IdentityRole>() // <-- Add this line
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • Добавьте некоторый код для заполнения ролей, утверждений и пользователей.Основная концепция этого примера кода состоит в том, что у меня есть две претензии: can_report позволяет держателю создавать отчеты, а can_test позволяет держателю запускать тесты.У меня есть две роли, Admin и Tester.Роль Tester может запускать тесты, но не может создавать отчеты.Роль Admin может выполнять обе задачи.Итак, я добавляю утверждения к ролям и создаю одного Admin тестового пользователя и одного Tester тестового пользователя.

    Сначала я добавляю класс, единственная цель которого в жизни - содержать константы, используемые в других местахэтот пример:

    // Contains constant strings used throughout this example
    public class MyApp
    {
        // Claims
        public const string CanTestClaim = "can_test";
        public const string CanReportClaim = "can_report";
    
        // Role names
        public const string AdminRole = "admin";
        public const string TesterRole = "tester";
    
        // Authorization policy names
        public const string CanTestPolicy = "can_test";
        public const string CanReportPolicy = "can_report";
    }
    

    Далее я собираю свои роли, утверждения и пользователей.Я поместил этот код в основной контроллер целевой страницы просто для удобства;он действительно принадлежит методу "startup" Configure, но это лишние полдюжины строк кода ...

    public class HomeController : Controller
    {
        const string Password = "QwertyA1?";
    
        const string AdminEmail = "admin@example.com";
        const string TesterEmail = "tester@example.com";
    
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly UserManager<ApplicationUser> _userManager;
    
        // Constructor (DI claptrap)
        public HomeController(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
        {
            _roleManager = roleManager;
            _userManager = userManager;
        }
    
        public async Task<IActionResult> Index()
        {
            // Initialize roles
            if (!await _roleManager.RoleExistsAsync(MyApp.AdminRole)) {
                var role = new IdentityRole(MyApp.AdminRole);
                await _roleManager.CreateAsync(role);
                await _roleManager.AddClaimAsync(role, new Claim(MyApp.CanTestClaim, ""));
                await _roleManager.AddClaimAsync(role, new Claim(MyApp.CanReportClaim, ""));
            }
    
            if (!await _roleManager.RoleExistsAsync(MyApp.TesterRole)) {
                var role = new IdentityRole(MyApp.TesterRole);
                await _roleManager.CreateAsync(role);
                await _roleManager.AddClaimAsync(role, new Claim(MyApp.CanTestClaim, ""));
            }
    
            // Initialize users
            var qry = _userManager.Users;
            IdentityResult result;
    
            if (await qry.Where(x => x.UserName == AdminEmail).FirstOrDefaultAsync() == null) {
                var user = new ApplicationUser {
                    UserName = AdminEmail,
                    Email = AdminEmail,
                    FullName = "Administrator"
                };
    
                result = await _userManager.CreateAsync(user, Password);
                if (!result.Succeeded) throw new InvalidOperationException(string.Join(" | ", result.Errors.Select(x => x.Description)));
    
                result = await _userManager.AddToRoleAsync(user, MyApp.AdminRole);
                if (!result.Succeeded) throw new InvalidOperationException(string.Join(" | ", result.Errors.Select(x => x.Description)));
            }
    
            if (await qry.Where(x => x.UserName == TesterEmail).FirstOrDefaultAsync() == null) {
                var user = new ApplicationUser {
                    UserName = TesterEmail,
                    Email = TesterEmail,
                    FullName = "Tester"
                };
    
                result = await _userManager.CreateAsync(user, Password);
                if (!result.Succeeded) throw new InvalidOperationException(string.Join(" | ", result.Errors.Select(x => x.Description)));
    
                result = await _userManager.AddToRoleAsync(user, MyApp.TesterRole);
                if (!result.Succeeded) throw new InvalidOperationException(string.Join(" | ", result.Errors.Select(x => x.Description)));
            }
    
            // Roles and Claims are in a cookie. Don't expect to see them in
            // the same request that creates them (i.e., the request that
            // executes the above code to create them). You need to refresh
            // the page to create a round-trip that includes the cookie.
            var admin = User.IsInRole(MyApp.AdminRole);
            var claims = User.Claims.ToList();
    
            return View();
        }
    
        [Authorize(policy: MyApp.CanTestPolicy)]
        public IActionResult Test()
        {
            return View();
        }
    
        [Authorize(policy: MyApp.CanReportPolicy)]
        public IActionResult Report()
        {
            return View();
        }
    
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
    

    , и я регистрирую свои политики аутентификации в подпрограмме "Startup" ConfigureServices, сразу после звонка на services.AddMvc

        // Register authorization policies
        services.AddAuthorization(options => {
            options.AddPolicy(MyApp.CanTestPolicy,   policy => policy.RequireClaim(MyApp.CanTestClaim));
            options.AddPolicy(MyApp.CanReportPolicy, policy => policy.RequireClaim(MyApp.CanReportClaim));
        });
    

Вот так.Теперь (при условии, что я отметил весь применимый код, который я добавил в проект выше), когда я запускаю приложение, я замечаю, что ни один из моих «встроенных» тестовых пользователей не может получить доступ ни к /home/Testили /home/Report стр.Более того, если я устанавливаю точку останова в методе Index, я вижу, что мои роли и утверждения не существуют в объекте User.Но я могу посмотреть на базу данных и увидеть все роли и претензии.

Ответы [ 3 ]

0 голосов
/ 13 ноября 2018

Итак, скажем, вопрос состоит в том, почему код, предоставленный шаблоном ASP.NET Core Web Application, не загружает роли или утверждения ролей в файл cookie, когда пользователь входит в систему.

После большого количества поиска в Googleи экспериментируя, как представляется, необходимо внести две модификации в шаблонный код, чтобы заставить роли и утверждения ролей работать:

Сначала необходимо добавить следующую строку кода в Startup.cs, чтобы включитьRoleManager.(Этот бит магии был упомянут в ОП.)

services.AddDefaultIdentity<ApplicationUser>()
   .AddRoles<IdentityRole>() // <-- Add this line
    .AddEntityFrameworkStores<ApplicationDbContext>();

Но подождите, это еще не все!В соответствии с этим обсуждением на GitHub , получение ролей и утверждений для отображения в cookie-файле включает либо возвращение к коду инициализации service.AddIdentity, либо использование service.AddDefaultIdentity и добавление этогострока кода в ConfigureServices:

// Add Role claims to the User object
// See: https://github.com/aspnet/Identity/issues/1813#issuecomment-420066501
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();

Если вы прочитаете обсуждение, упомянутое выше, вы увидите, что Роли и Ролевые утверждения явно устарели или, по крайней мере, не поддерживаются.Лично я считаю действительно полезным назначать заявки на роли, назначать роли пользователям, а затем принимать решения об авторизации на основе заявок (которые предоставляются пользователям на основе их ролей).Это дает мне простой декларативный способ, например, разрешить доступ к одной функции нескольким ролям (т. Е. Всем ролям, содержащим утверждение, используемое для включения этой функции).

Но вы действительно хотитеобратите внимание на количество ролей и данных претензий, передаваемых в файле cookie для аутентификации.Чем больше данных, тем больше байтов отправляется на сервер с каждым запросом, и я понятия не имею, что происходит, когда вы сталкиваетесь с каким-то ограничением размера куки.

0 голосов
/ 06 июня 2019

Также вы можете попробовать исправить Аутентификацию вот так

services.AddDefaultIdentity<ApplicationUser>()
    .AddRoles<IdentityRole>()
    .AddRoleManager<RoleManager<IdentityRole>>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
0 голосов
/ 13 ноября 2018

Ааа, есть некоторые изменения с ASP.NET Core версии 2.0 до 2.1.AddDefaultIdentity является единственным.

Я не знаю, с чего начать с вашего кода, поэтому я приведу пример для создания и получения пользовательских ролей.

Давайте создадимUserRoles first:

public enum UserRoles
{
    [Display(Name = "Quản trị viên")]
    Administrator = 0,

    [Display(Name = "Kiểm soát viên")]
    Moderator = 1,

    [Display(Name = "Thành viên")]
    Member = 2
}

Примечание : Вы можете удалить атрибут Display.

Затем мы создадим RolesExtensions class:

public static class RolesExtensions
{
    public static async Task InitializeAsync(RoleManager<IdentityRole> roleManager)
    {
        foreach (string roleName in Enum.GetNames(typeof(UserRoles)))
        {
            if (!await roleManager.RoleExistsAsync(roleName))
            {
                await roleManager.CreateAsync(new IdentityRole(roleName));
            }
        }
    }
}

Далее в классе Startup.cs мы запускаем его:

    public void Configure(
        IApplicationBuilder app, 
        IHostingEnvironment env, 
        RoleManager<IdentityRole> roleManager)
    {
        // other settings...

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

        var task = RolesExtensions.InitializeAsync(roleManager);
        task.Wait();
    }

Примечание : Configure требует возвращаемый тип void, поэтому нам нужносоздайте задачу для инициализации пользовательских ролей, и мы вызываем метод Wait.

Не изменяйте возвращаемый тип следующим образом:

public async void Configure(...)
{
    await RolesExtensions.InitializeAsync(roleManager);
}

Источник: Async / Await - Лучшие практики асинхронного программирования

В методе ConfigureServices эти конфигурации НЕ будут работать (мы не можем правильно использовать User.IsInRole):

services.AddDefaultIdentity<ApplicationUser>()
    //.AddRoles<IdentityRole>()
    //.AddRoleManager<RoleManager<IdentityRole>>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Я не знаю почему, но AddRoles и AddRoleManager не поддерживают проверку роли для пользователя (User.IsInRole).

В этом случае нам необходимо зарегистрироватьсясервис, как это:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Используя этот способ, шМы создаем 3 роли пользователя в базе данных:

1

При регистрации нового пользователя нам просто нужно позвонить:

await _userManager.AddToRoleAsync(user, nameof(UserRoles.Administrator));

Наконец, мы можем использовать [Authorize(Roles = "Administrator")] и:

if (User.IsInRole("Administrator"))
{
    // authorized
}

// or
if (User.IsInRole(nameof(UserRoles.Administrator)))
{
    // authorized
}

// but
if (User.IsInRole("ADMINISTRATOR"))
{
    // authorized
}

P / S: Есть много вещей, которые необходимо реализовать для достижения этой цели.Так что, может быть, я что-то пропустил в этом примере.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...