Asp.net-Core

ASP.NET Core Identity 2:User.IsInRole 總是返回 false

  • February 17, 2022

**問題:**我呼叫RoleManager.CreateAsync()RoleManager.AddClaimAsync()創建角色和相關的角色聲明。然後我打電話UserManager.AddToRoleAsync()給這些角色添加使用者。但是當使用者登錄時,角色和相關聲明都不會出現在ClaimsPrincipal(即控制器的User對象)中。這樣做的結果是User.IsInRole()總是返回false,並且返回的Claims集合User.Claims不包含角色聲明,並且[Authorize(policy: xxx)]註解不起作用。

我還應該補充一點,一種解決方案是從使用 new services.AddDefaultIdentity()(由模板化程式碼提供)恢復為呼叫services.AddIdentity().AddSomething().AddSomethingElse(). 我不想去那裡,因為我在網上看到了太多關於我需要AddIdentity為各種案例進行配置的相互矛盾的故事。AddDefaultIdentity似乎在沒有大量添加流暢配置的情況下正確地完成了大多數事情。

順便說一句,我問這個問題是為了回答它……除非別人給我的答案比我準備發布的更好。我也在問這個問題,因為經過幾週的搜尋,我還沒有找到一個在 ASP.NET Core Identity 2 中創建和使用角色和聲明的好的端到端範例。希望這個問題中的程式碼範例可以幫助其他偶然發現它的人……

設置: 我創建了一個新的 ASP.NET Core Web 應用程序,選擇 Web 應用程序(模型-視圖-控制器),並將身份驗證更改為個人使用者帳戶。在生成的項目中,我執行以下操作:

  • 在包管理器控制台中,更新數據庫以匹配腳手架遷移:

更新數據庫

  • 添加一個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實體。

添加遷移 m_001

更新數據庫

  • 添加以下程式碼行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";
}

接下來,我播種我的角色、聲明和使用者。我把這段程式碼放在主登陸頁面控制器中只是為了方便;它確實屬於“啟動”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

   // 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。但我可以查看數據庫並查看所有角色和聲明。

因此,回顧一下,問題詢問為什麼 ASP.NET Core Web 應用程序模板提供的程式碼不會在使用者登錄時將角色或角色聲明載入到 cookie 中。

經過大量的Google搜尋和試驗,似乎必須對模板程式碼進行兩項修改才能使角色和角色聲明正常工作:

首先,您必須在 Startup.cs 中添加以下程式碼行以啟用 RoleManager。(OP中提到了這一點魔法。)

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 中攜帶的角色和聲明數據的數量。更多數據意味著每個請求發送到伺服器的字節數更多,我不知道當你遇到某種 cookie 大小限制時會發生什麼。

引用自:https://stackoverflow.com/questions/53271496