ASP.NET Core身份2:User.IsInRole始终返回false

时间:2018-11-12 23:17:50

标签: asp.net-core asp.net-core-mvc asp.net-identity

问题:我打电话给RoleManager.CreateAsync()RoleManager.AddClaimAsync()来创建角色和相关的角色声明。然后,我致电UserManager.AddToRoleAsync()将用户添加到这些角色。但是,当用户登录时,角色和关联的声明都不会显示在ClaimsPrincipal中(即,控制器的User对象)。这样的结果是User.IsInRole()总是返回false,并且User.Claims返回的Claims集合不包含角色声明,并且[Authorize(policy: xxx)]注释也不起作用。

我还应该补充一点,一种解决方案是从使用新的services.AddDefaultIdentity()(由模板代码提供)恢复为调用services.AddIdentity().AddSomething().AddSomethingElse()。我不想去那里,因为我在网上看到太多相互矛盾的故事,这些故事说明我需要为各种用例配置AddIdentity时需要做什么。 AddDefaultIdentity似乎可以正确地执行大多数操作,而无需增加许多流畅的配置。

顺便说一句,我问这个问题是为了回答这个问题……除非别人给我比我准备发布的答案更好的答案。我也问这个问题,因为经过几周的搜索,我还没有找到一个很好的端到端示例,该示例在ASP.NET Core Identity 2中创建和使用角色和声明。希望这个问题中的代码示例可以对偶然发现它的人有所帮助...

设置: 我创建了一个新的ASP.NET Core Web应用程序,选择Web应用程序(模型-视图-控制器),然后将身份验证更改为单个用户帐户。在结果项目中,我执行以下操作:

  • 在Package Manager控制台中,更新数据库以匹配支架式迁移:

      

    更新数据库

  • 添加一个扩展ApplicationUser的{​​{1}}类。这涉及添加类,在IdentityUser中添加一行代码,并在项目中的每个地方都将ApplicationDbContext的每个实例替换为<IdentityUser>

    新的<ApplicationUser>类:

    ApplicationUser

    更新后的public class ApplicationUser : IdentityUser { public string FullName { get; set; } } 类:

    ApplicationDbContext
  • 在Package Manager控制台中,创建新的迁移并更新数据库以合并public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } // Add this line of code public DbSet<ApplicationUser> ApplicationUsers { get; set; } } 实体。

      

    添加迁移m_001
      更新数据库

  • ApplicationUsers中添加以下代码行以启用Startup.cs

    RoleManager
  • 向种子角色,声明和用户添加一些代码。此示例代码的基本概念是我有两个主张:services.AddDefaultIdentity<ApplicationUser>() .AddRoles<IdentityRole>() // <-- Add this line .AddEntityFrameworkStores<ApplicationDbContext>(); 允许持有人创建报告,而can_report允许持有人运行测试。我有两个角色,can_testAdminTester角色可以运行测试,但不能创建报告。 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"; } 方法中,但这是额外的六行代码...

    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 }); } }

    之后,我在“启动” ConfigureServices例程中注册了身份验证策略。
    services.AddMvc

he。现在,(假设我已经注意到上面已经添加到项目中的所有适用代码),当我运行该应用程序时,我注意到我的“内置”测试用户都无法访问{{1 }}或 // Register authorization policies services.AddAuthorization(options => { options.AddPolicy(MyApp.CanTestPolicy, policy => policy.RequireClaim(MyApp.CanTestClaim)); options.AddPolicy(MyApp.CanReportPolicy, policy => policy.RequireClaim(MyApp.CanReportClaim)); }); 页。此外,如果我在Index方法中设置了一个断点,则会发现我的角色和声明在/home/Test对象中不存在。但是我可以查看数据库,并查看所有角色和声明。

3 个答案:

答案 0 :(得分:5)

总而言之,这个问题问为什么当用户登录时ASP.NET Core Web应用程序模板提供的代码为什么不将角色或角色声明加载到cookie中。

经过大量的Google搜索和实验之后,似乎必须对模板化代码进行两项修改才能使Roles和Role Claims起作用:

首先,必须在Startup.cs中添加以下代码行才能启用RoleManager。 (OP中提到了这一点魔术。)

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

但是,等等,还有更多!根据{{​​3}},要获得要显示在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>>();

如果您阅读了以上引用的讨论,您会发现Roles和Role Claims显然已被弃用,或者至少不被急切支持。就个人而言,我发现将权利分配给角色,将角色分配给用户,然后根据声明(根据用户的角色授予用户)做出授权决策确实很有用。这为我提供了一种简单的声明方式,例如允许一个功能由多个角色(即,包含用于启用该功能的声明的所有角色)访问。

但是您确实要注意角色的数量,并声明auth cookie中携带的数据。更多的数据意味着每个请求将更多的字节发送到服务器,并且我不知道当您遇到某种Cookie大小限制时会发生什么。

答案 1 :(得分:2)

啊,从ASP.NET Core 2.0版到2.1版有一些更改。 AddDefaultIdentity是一个。

我不知道从代码的何处开始,因此,我将提供一个示例来创建和获取用户角色。

首先创建UserRoles

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类:

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方法。 / p>

请勿更改返回的类型,如下所示:

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

来源:Async/Await - Best Practices in Asynchronous Programming

ConfigureServices方法中,这些配置将无法正常工作(我们无法正确使用User.IsInRole):

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

我不知道为什么,但是AddRolesAddRoleManager不支持检查用户(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:要实现此目标,需要执行很多事情。所以也许我在这个例子中错过了一些东西。

答案 2 :(得分:0)

您还可以尝试像这样修复身份验证

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;
});