質問:RoleManager.CreateAsync()
とRoleManager.AddClaimAsync()
を呼び出して、ロールと関連するロールクレームを作成します。次に、UserManager.AddToRoleAsync()
を呼び出して、これらのロールにユーザーを追加します。ただし、ユーザーがログインすると、ロールも関連するクレームもClaimsPrincipal
(つまり、コントローラーのUser
オブジェクト)に表示されません。その結果、User.IsInRole()
は常にfalseを返し、User.Claims
によって返されるクレームのコレクションにはロールクレームが含まれず、[Authorize(policy: xxx)]
アノテーションは機能しません。
また、1つの解決策は、新しいservices.AddDefaultIdentity()
(テンプレート化されたコードによって提供される)の使用からservices.AddIdentity().AddSomething().AddSomethingElse()
の呼び出しに戻すことです。さまざまなユースケースに合わせてAddIdentity
を構成するために何をする必要があるかについて、オンラインで相反する話をあまりにも多く見たので、そこには行きたくありません。 AddDefaultIdentity
は、多くの流暢な構成を追加しなくても、ほとんどのことを正しく実行しているようです。
ところで、私はそれに答えるつもりでこの質問をしています...誰かが私が投稿する準備ができているものよりも良い答えを私に与えない限り。 数週間の検索の後、ASP.NET Core Identity 2でロールとクレームを作成および使用するための優れたエンドツーエンドの例をまだ見つけていませんという理由で、この質問もしています。うまくいけば、この質問のコード例は、それにつまずいた他の誰かを助けるかもしれません...
セットアップ:新しいASP.NETコアWebアプリケーションを作成し、Webアプリケーション(Model-View-Controller)を選択して、認証を個別のユーザーアカウントに変更します。結果のプロジェクトでは、次のことを行います。
パッケージマネージャーコンソールで、スキャフォールド移行に一致するようにデータベースを更新します。
データベースを更新する
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-migration m_001
データベースを更新する
Startup.cs
に次のコード行を追加して、RoleManager
を有効にします
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
シードの役割、クレーム、およびユーザーにコードを追加します。このサンプルコードの基本的な概念は、2つの主張があるということです。can_report
は所有者がレポートを作成できるようにし、can_test
は所有者がテストを実行できるようにします。 Admin
とTester
の2つの役割があります。 Tester
ロールはテストを実行できますが、レポートを作成することはできません。 Admin
ロールは両方を実行できます。そこで、ロールにクレームを追加し、1人のAdmin
テストユーザーと1人の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 = "[email protected]";
const string TesterEmail = "[email protected]";
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 });
}
}
services.AddMvc
を呼び出した直後に、認証ポリシーを「スタートアップ」ConfigureServices
ルーチンに登録します。
// 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にロードしない理由を尋ねます。
多くのグーグルと実験の後、ロールとロールクレームを機能させるために、テンプレート化されたコードに2つの変更を加える必要があるようです。
まず、RoleManagerを有効にするには、Startup.csに次のコード行を追加する必要があります。 (このちょっとした魔法は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>>();
_
上記の説明を読むと、ロールとロールクレームは明らかに非推奨であるか、少なくとも熱心にサポートされていないことがわかります。個人的には、ロールにクレームを割り当て、ユーザーにロールを割り当て、クレーム(ロールに基づいてユーザーに付与される)に基づいて承認を決定することが非常に便利だと思います。これにより、たとえば、1つの関数に複数のロール(つまり、その関数を有効にするために使用されるクレームを含むすべてのロール)からアクセスできるようにする、簡単で宣言的な方法が得られます。
ただし、認証Cookieで運ばれる役割とクレームデータの量に注意を払う必要があります。より多くのデータは、各リクエストでサーバーに送信されるより多くのバイトを意味します。Cookieサイズのある種の制限にぶつかったときに何が起こるか、私にはわかりません。
ああ、ASP.NETCoreバージョン2.0から2.1への変更がいくつかあります。 AddDefaultIdentity
が1つです。
コードからどこから始めればよいかわからないので、ユーザーロールを作成して取得する例を示します。
最初に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
方法。
返されたタイプを次のように変更しないでください:
_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つのユーザーロールを作成します。
新しいユーザーを登録するときは、次の電話をかけるだけです。
_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:この目標を達成するために実装する必要のあることがたくさんあります。したがって、この例では何かを見逃した可能性があります。
また、このように認証を修正することもできます
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;
});