Bob*_*lth 5 asp.net-identity asp.net-core-mvc asp.net-core
问题:我打电话给我,RoleManager.CreateAsync()
并RoleManager.AddClaimAsync()
创建角色和相关的角色声明。然后,我打电话UserManager.AddToRoleAsync()
给这些角色添加用户。但是,当用户登录时,角色和关联的声明都不会显示在ClaimsPrincipal
(即控制器的User
对象)中。这样做的结果是User.IsInRole()
始终返回false,并且由所返回的Claims集合User.Claims
不包含角色声明,并且[Authorize(policy: xxx)]
注释不起作用。
我还应该补充一点,一种解决方案是从使用新的services.AddDefaultIdentity()
(由模板代码提供)恢复为call 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; }
}
Run Code Online (Sandbox Code Playgroud)
更新的ApplicationDbContext
类:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
// Add this line of code
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)在Package Manager控制台中,创建一个新的迁移并更新数据库以合并该ApplicationUsers
实体。
add-migration m_001
更新数据库
在其中添加以下代码行Startup.cs
以启用RoleManager
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
Run Code Online (Sandbox Code Playgroud)向种子角色,声明和用户添加一些代码。此示例代码的基本概念是我有两个主张: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";
}
Run Code Online (Sandbox Code Playgroud)
接下来,我播种我的角色,声明和用户。出于方便起见,我将此代码放在主目标网页控制器中。它确实属于“启动” 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 });
}
}
Run Code Online (Sandbox Code Playgroud)
并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));
});
Run Code Online (Sandbox Code Playgroud)ew。现在,(假设我已经注意到了上面添加到项目中的所有适用代码),当我运行该应用程序时,我注意到我的“内置”测试用户都无法访问/home/Test
或/home/Report
页面。此外,如果我在Index方法中设置了一个断点,则会发现我的角色和声明不存在于User
对象中。但是我可以查看数据库,并查看所有角色和声明。
因此,总而言之,这个问题问为什么当用户登录时,ASP.NET Core Web应用程序模板提供的代码不会将角色或角色声明加载到cookie中。
经过大量的Google搜索和试验之后,似乎必须对模板化代码进行两项修改才能使Roles和Role Claims起作用:
首先,必须在Startup.cs中添加以下代码行才能启用RoleManager。(OP中提到了这一点魔术。)
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
Run Code Online (Sandbox Code Playgroud)
但是,等等,还有更多!据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>>();
Run Code Online (Sandbox Code Playgroud)
如果阅读了以上引用的讨论,您会发现Roles和Role Claims显然已经过时,或者至少不急于得到支持。就个人而言,我发现将权利分配给角色,将角色分配给用户,然后根据声明(根据用户的角色授予用户)做出授权决策确实很有用。这为我提供了一种简单的声明性方法,例如,允许多个角色(即,包含用于启用该功能的声明的所有角色)访问一个功能。
但是您确实要注意角色的数量,并声明身份验证cookie中携带的数据。更多数据意味着每个请求将更多字节发送给服务器,而且我不知道当您遇到某种Cookie大小限制时会发生什么。
啊,从 ASP.NET Core 2.0 版到 2.1 版有一些变化。AddDefaultIdentity
是这个。
我不知道从您的代码从哪里开始,因此,我将提供一个示例来创建和获取用户角色。
我们UserRoles
先创建:
public enum UserRoles
{
[Display(Name = "Qua?n tri? viên")]
Administrator = 0,
[Display(Name = "Kiê?m soa?t viên")]
Moderator = 1,
[Display(Name = "Tha?nh viên")]
Member = 2
}
Run Code Online (Sandbox Code Playgroud)
注意:您可以删除该属性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));
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
接下来,在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();
}
Run Code Online (Sandbox Code Playgroud)
注意:Configure
需要一个返回类型void
,所以我们需要创建一个任务来初始化用户角色并调用Wait
方法。
不要像这样更改返回的类型:
public async void Configure(...)
{
await RolesExtensions.InitializeAsync(roleManager);
}
Run Code Online (Sandbox Code Playgroud)
在该ConfigureServices
方法中,这些配置不起作用(我们无法User.IsInRole
正确使用):
services.AddDefaultIdentity<ApplicationUser>()
//.AddRoles<IdentityRole>()
//.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Run Code Online (Sandbox Code Playgroud)
我不知道为什么,但AddRoles
和AddRoleManager
不支持检查角色的用户(User.IsInRole
)。
在这种情况下,我们需要像这样注册服务:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Run Code Online (Sandbox Code Playgroud)
通过这种方式,我们在数据库中创建了 3 个用户角色:
注册新用户时,我们只需要调用:
await _userManager.AddToRoleAsync(user, nameof(UserRoles.Administrator));
Run Code Online (Sandbox Code Playgroud)
最后,我们可以使用[Authorize(Roles = "Administrator")]
和:
if (User.IsInRole("Administrator"))
{
// authorized
}
// or
if (User.IsInRole(nameof(UserRoles.Administrator)))
{
// authorized
}
// but
if (User.IsInRole("ADMINISTRATOR"))
{
// authorized
}
Run Code Online (Sandbox Code Playgroud)
P/S:要实现这个目标,有很多事情需要实施。所以也许我在这个例子中遗漏了一些东西。
归档时间: |
|
查看次数: |
2769 次 |
最近记录: |