その下で、 ロールベースの承認 と 要求ベースの承認 では、要件、要件ハンドラー、および構成済みポリシーが使用されます。 これらの構成要素では、コードでの認可評価の式がサポートされています。 その結果、多機能で再利用できるテスト可能な認可構造が作成されます。
認可ポリシーは、1 つまたは複数の要件で構成されます。 それを、アプリの Program.cs ファイルで、認可サービス構成の一部として登録します。
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
前の例では、"AtLeast21" ポリシーが作成されます。 これには最低年齢に関する 1 つの要件があり、パラメーターとして要件に提供されます。
IAuthorizationService
認可が成功したかどうかを決定するプライマリ サービスは IAuthorizationService です。
/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Checks if a user meets a specific set of requirements for the specified resource
/// </summary>
/// <param name="user">The user to evaluate the requirements against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="requirements">The requirements to evaluate.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// This value is <value>true</value> when the user fulfills the policy;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
IEnumerable<IAuthorizationRequirement> requirements);
/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="policyName">The name of the policy to check against a specific
/// context.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// Returns a flag indicating whether the user, and optional resource has fulfilled
/// the policy.
/// <value>true</value> when the policy has been fulfilled;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user, object resource, string policyName);
}
上記のコードでは、IAuthorizationService の 2 つのメソッドが強調表示されています。
IAuthorizationRequirement は、メソッドのないマーカー インターフェイスであり、承認が成功したかどうかを追跡するためのメカニズムです。
各 IAuthorizationHandler によって、要件が満たされているかどうかがチェックされます。
/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization information.</param>
Task HandleAsync(AuthorizationHandlerContext context);
}
AuthorizationHandlerContext クラスは、要件が満たされているかどうかをマークするためにハンドラーによって使用されます。
context.Succeed(requirement)
次のコードは、認可サービスの既定の実装を簡単に (コメントで注釈を付けて) 示したものです。
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
// Create a tracking context from the authorization inputs.
var authContext = _contextFactory.CreateContext(requirements, user, resource);
// By default this returns an IEnumerable<IAuthorizationHandler> from DI.
var handlers = await _handlers.GetHandlersAsync(authContext);
// Invoke all handlers.
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
}
// Check the context, by default success is when all requirements have been met.
return _evaluator.Evaluate(authContext);
}
次に示すコードは、一般的な認可サービスの構成です。
// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...
builder.Services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
// Configure your policies
builder.Services.AddAuthorization(options =>
options.AddPolicy("Something",
policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
認可には、IAuthorizationService、[Authorize(Policy = "Something")]、または RequireAuthorization("Something") を使います。
MVC コントローラーにポリシーを適用する
Razor Pages を使うアプリについては、「Razor Pages にポリシーを適用する」セクションをご覧ください。
コントローラーにポリシーを適用するには、[Authorize] 属性を使用してポリシー名を指定します:
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
public IActionResult Index() => View();
}
コントローラーとアクションのレベルで複数のポリシーが適用されている場合、すべてのポリシーはアクセスが許可される前に通過する必要があります。
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller2 : Controller
{
[Authorize(Policy = "IdentificationValidated")]
public IActionResult Index() => View();
}
Razor Pages にポリシーを適用する
Razor Pages にポリシーを適用するには、[Authorize] 属性を使用してポリシー名を指定します。 例えば次が挙げられます。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationPoliciesSample.Pages;
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }
ポリシーは Page ハンドラー レベルでは適用できません。Page に適用する必要があります。Razor
Razor Pages にポリシーを適用するには、認可規約を使用することもできます。
エンドポイントにポリシーを適用する
エンドポイントにポリシーを適用するには、RequireAuthorization を使用してポリシー名を指定します。 例えば次が挙げられます。
app.MapGet("/helloworld", () => "Hello World!")
.RequireAuthorization("AtLeast21");
Requirements
認可要件は、ポリシーで現在のユーザー プリンシパルを評価するために使用できるデータ パラメーターのコレクションです。 "AtLeast21" ポリシーの要件は、1 つのパラメーターつまり最低年齢です。 要件は、空のマーカー インターフェイスである IAuthorizationRequirement を実装します。 パラメーター化された最低年齢要件は、次のように実装できます。
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Requirements;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int minimumAge) =>
MinimumAge = minimumAge;
public int MinimumAge { get; }
}
認可ポリシーに複数の認可要件が含まれている場合、ポリシーの評価が成功するには、すべての要件が合格する必要があります。 つまり、1 つの認可ポリシーに追加された複数の認可要件は、AND ベースで処理されます。
Note
要件には、データまたはプロパティがなくてもかまいません。
認可ハンドラー
認可ハンドラーでは、要件のプロパティの評価が行われます。 承認ハンドラーは、指定されたAuthorizationHandlerContextに対して要件を評価し、accessが許可されているかどうかを判断します。
1 つの要件に複数のハンドラーを指定できます。 ハンドラーは AuthorizationHandler<TRequirement> を継承できます。ここで TRequirement は処理される要件です。 または、ハンドラーで IAuthorizationHandler を直接実装して、複数の種類の要件を処理することもできます。
1 つの要件にハンドラーを使用する
次の例では、最低年齢ハンドラーで 1 つの要件を処理する 1 対 1 の関係が示されています。
using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Handlers;
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(
c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");
if (dateOfBirthClaim is null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
上記のコードにより、現在のユーザー プリンシパルに、既知の信頼された発行者によって発行された生年月日クレームがあるかどうかが判断されます。 クレームが見つからないと認可は行われず、その場合は完了したタスクが返されます。 クレームが存在する場合は、ユーザーの年齢が計算されます。 要件で定義されている最低年齢をユーザーが満たしている場合、認可は成功と見なされます。 認可が成功すると、満たされた要件を唯一のパラメーターとして context.Succeed が呼び出されます。
複数の要件にハンドラーを使用する
次の例は、アクセス許可ハンドラーで 3 種類の異なる要件を処理できる一対多の関係を示したものです。
using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Handlers;
public class PermissionHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is ReadPermission)
{
if (IsOwner(context.User, context.Resource)
|| IsSponsor(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditPermission || requirement is DeletePermission)
{
if (IsOwner(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
private static bool IsOwner(ClaimsPrincipal user, object? resource)
{
// Code omitted for brevity
return true;
}
private static bool IsSponsor(ClaimsPrincipal user, object? resource)
{
// Code omitted for brevity
return true;
}
}
上のコードでは、成功とマークされていない要件が含まれる PendingRequirements プロパティが走査されます。
ReadPermission要件の場合、要求されたリソースをaccessするには、ユーザーが所有者またはスポンサーである必要があります。
EditPermission または DeletePermission の要件の場合、要求されたリソースにアクセスするための所有者である必要があります。
ハンドラーの登録
構成の間にサービス コレクションでハンドラーを登録します。 例えば次が挙げられます。
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
上のコードでは、MinimumAgeHandler がシングルトンとして登録されます。 ハンドラーは、組み込みのサービス有効期間のいずれかを使って登録できます。
IAuthorizationRequirement と IAuthorizationHandler の両方を実装する 1 つのクラスに、要件とハンドラーの両方をバンドルすることができます。 これにより、ハンドラーと要件の間に緊密な結合が作成されるので、単純な要件とハンドラーにのみ推奨されます。 両方のインターフェイスを実装するクラスを作成すると、要件で自身を処理できる組み込みの PassThroughAuthorizationHandler があるため、DI にハンドラーを登録する必要がなくなります。
AssertionRequirementが要件であり、完全に自己完結型クラスのハンドラーである良い例については、AssertionRequirement クラスの実装を参照してください。
ハンドラーは何を返すべきか?
Handleにおけるメソッドは値を返さないことに注意してください。 成功または失敗の状態はどのようにして示されるのでしょうか。
ハンドラーは、
context.Succeed(IAuthorizationRequirement requirement)を呼び出し、検証が成功した要件を渡すことによって、成功を示します。同じ要件が他のハンドラーで成功する可能性があるので、一般に、ハンドラーで失敗を処理する必要はありません。
他の要件ハンドラーが成功した場合でも、失敗を保証するには、
context.Failを呼び出します。
あるハンドラーで context.Succeed または context.Fail が呼び出された場合でも、他のすべてのハンドラーが呼び出されます。 これにより、別のハンドラーで検証が成功したり要件が失敗した場合でも発生するログ記録などの副作用を、要件で生成できます。
false に設定すると、InvokeHandlersAfterFailure プロパティにより、context.Fail が呼び出されたときのハンドラーの実行が省略されます。
InvokeHandlersAfterFailure の既定値は true で、すべてのハンドラーが呼び出されます。
Note
認可ハンドラーは、認証が失敗した場合でも呼び出されます。 また、ハンドラーは任意の順序で実行できるため、特定の順序で呼び出されることに依存させないでください。
要件に対して複数のハンドラーが必要な理由
評価を OR ベースで行いたい場合は、1 つの要件に対して複数のハンドラーを実装します。 たとえば、Microsoftには、キー カードでのみ開くドアがあります。 鍵カードを自宅に置いておくと、受付の方が一時ステッカーを印刷してドアを開けます。 このシナリオでは、要件は BuildingEntry の 1 つですが、それぞれが 1 つの要件を調べる複数のハンドラーを使用します。
BuildingEntryRequirement.cs
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Requirements;
public class BuildingEntryRequirement : IAuthorizationRequirement { }
BadgeEntryHandler.cs
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Handlers;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(
c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
TemporaryStickerHandler.cs
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Handlers;
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(
c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
{
// Code to check expiration date omitted for brevity.
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
両方のハンドラーを登録する必要があります。 ポリシーで BuildingEntryRequirement が評価されるときに、いずれかのハンドラーが成功した場合、ポリシーの評価は成功します。
func を使用してポリシーを満たす
コードで表現してポリシーを簡単に満たすことができる場合があります。
Func<AuthorizationHandlerContext, bool> ポリシー ビルダーでポリシーを構成するときに、RequireAssertion を指定できます。
たとえば、前の BadgeEntryHandler を次のように書き換えることができます。
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context => context.User.HasClaim(c =>
(c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
&& c.Issuer == "https://microsoftsecurity")));
});
ハンドラーで MVC 要求コンテキストをAccessする
HandleRequirementAsync メソッドには 2 つのパラメーターがあります。AuthorizationHandlerContext と、処理対象の TRequirement です。 MVC や SignalR などのフレームワークにより、Resource の AuthorizationHandlerContext プロパティに任意のオブジェクトが自由に追加されて、追加の情報が渡されます。
エンドポイント ルーティングを使用している場合、通常、認可は認可ミドルウェアによって処理されます。 この場合、Resource プロパティは HttpContext のインスタンスです。 コンテキストを使用して現在のエンドポイントをaccessできます。これは、ルーティング先の基になるリソースをプローブするために使用できます。 例えば次が挙げられます。
if (context.Resource is HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
...
}
従来のルーティングでは、または MVC の認可フィルターの一部として認可が行われるときは、Resource の値は AuthorizationFilterContext のインスタンスです。 このプロパティは、HttpContext、RouteData、MVC、およびRazorPagesによって提供されるその他すべてのアクセスを提供します。
Resource プロパティの使用はフレームワーク固有です。
Resource プロパティの情報を使用すると、認可ポリシーが特定のフレームワークに制限されます。
Resource キーワードを使って is プロパティをキャストしてから、キャストが成功したことを確認して、他のフレームワークで実行したときにコードが InvalidCastException でクラッシュしないようにする必要があります。
// Requires the following import:
// using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
}
すべてのユーザーの認証をグローバルに要求する
すべてのアプリ ユーザーに認証を要求する方法については、「承認によって保護されたユーザー データを使用して ASP.NET Core アプリを作成するを参照してください。
外部サービスを使用した認可のサンプル
AspNetCore.Docs.Samples のサンプル コードは、外部承認サービスで追加の承認要件を実装する方法を示しています。 サンプル Contoso.API プロジェクトは、Azure AD で保護されています。
Contoso.Security.API project からの追加の承認チェックでは、Contoso.API クライアント アプリが GetWeather API を呼び出すことができるかどうかを示すペイロードが返されます。
サンプルの構成
Microsoft Entra ID テナント にアプリケーション登録 を作成します。AppRole を割り当てます。
[API のアクセス許可] で、AppRole をアクセス許可として追加し、管理者の同意を付与します。 このセットアップでは、このアプリ登録は API と API を呼び出すクライアントの両方を表すことに注意してください。 必要に応じて、2 つのapp registrationsを作成できます。 このセットアップを使用している場合は、必ず API のアクセス許可のみを実行し、クライアントのみのアクセス許可ステップとして AppRole を追加してください。 クライアント アプリの登録でのみ、クライアント シークレットを生成する必要があります。
次の設定で
Contoso.APIproject を構成します。
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<Tenant name from AAD properties>.onmicrosoft.com",
"TenantId": "<Tenant Id from AAD properties>",
"ClientId": "<Client Id from App Registration representing the API>"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
- 次の設定で
Contoso.Security.APIを構成します。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AllowedClients": [
"<Use the appropriate Client Id representing the Client calling the API>"
]
}
ContosoAPI.collection.json ファイルを開き、次のように環境を構成します。
-
ClientId: API を呼び出すクライアントを表すアプリ登録からのクライアント ID。 -
clientSecret: API を呼び出すクライアントを表すアプリ登録からのクライアント シークレット。 -
TenantId: AAD プロパティのテナント ID
-
ContosoAPI.collection.jsonファイルからコマンドを抽出し、これを使用してアプリをテストする cURL コマンドを作成します。ソリューションを実行し、cURL を使用して API を呼び出します。
Contoso.Security.API.SecurityPolicyControllerにブレークポイントを追加し、Get Weather が許可されているかどうかをアサートするために使用されるクライアント ID が渡されていることを確認できます。
その他のリソース
その下で、 ロールベースの承認 と 要求ベースの承認 では、要件、要件ハンドラー、および事前構成済みのポリシーが使用されます。 これらの構成要素では、コードでの認可評価の式がサポートされています。 その結果、多機能で再利用できるテスト可能な認可構造が作成されます。
認可ポリシーは、1 つまたは複数の要件で構成されます。 それは、Startup.ConfigureServices メソッドで認可サービス構成の一部として登録されます。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
}
前の例では、"AtLeast21" ポリシーが作成されます。 これには最低年齢に関する 1 つの要件があり、パラメーターとして要件に提供されます。
IAuthorizationService
認可が成功したかどうかを決定するプライマリ サービスは IAuthorizationService です。
/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Checks if a user meets a specific set of requirements for the specified resource
/// </summary>
/// <param name="user">The user to evaluate the requirements against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="requirements">The requirements to evaluate.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// This value is <value>true</value> when the user fulfills the policy;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
IEnumerable<IAuthorizationRequirement> requirements);
/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="policyName">The name of the policy to check against a specific
/// context.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// Returns a flag indicating whether the user, and optional resource has fulfilled
/// the policy.
/// <value>true</value> when the policy has been fulfilled;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user, object resource, string policyName);
}
上記のコードでは、IAuthorizationService の 2 つのメソッドが強調表示されています。
IAuthorizationRequirement は、メソッドのないマーカー インターフェイスであり、承認が成功したかどうかを追跡するためのメカニズムです。
各 IAuthorizationHandler によって、要件が満たされているかどうかがチェックされます。
/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization information.</param>
Task HandleAsync(AuthorizationHandlerContext context);
}
AuthorizationHandlerContext クラスは、要件が満たされているかどうかをマークするためにハンドラーによって使用されます。
context.Succeed(requirement)
次のコードは、認可サービスの既定の実装を簡単に (コメントで注釈を付けて) 示したものです。
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
// Create a tracking context from the authorization inputs.
var authContext = _contextFactory.CreateContext(requirements, user, resource);
// By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
var handlers = await _handlers.GetHandlersAsync(authContext);
// Invoke all handlers.
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
}
// Check the context, by default success is when all requirements have been met.
return _evaluator.Evaluate(authContext);
}
次のコードは、一般的な ConfigureServices を示したものです。
public void ConfigureServices(IServiceCollection services)
{
// Add all of your handlers to DI.
services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...
services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
// Configure your policies
services.AddAuthorization(options =>
options.AddPolicy("Something",
policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
services.AddControllersWithViews();
services.AddRazorPages();
}
認可には、IAuthorizationService または [Authorize(Policy = "Something")] を使います。
MVC コントローラーにポリシーを適用する
Razor Pages を使っている場合は、このドキュメントの「Razor Pages にポリシーを適用する」をご覧ください。
ポリシーは、ポリシー名と共に [Authorize] 属性を使用することでコントローラーに適用されます。 例えば次が挙げられます。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Index() => View();
}
Razor Pages にポリシーを適用する
Razor Pages にポリシーを適用するには、[Authorize] 属性を使用してポリシー名を指定します。 例えば次が挙げられます。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}
ポリシーは Page ハンドラー レベルでは適用できません。Page に適用する必要があります。Razor
Razor Pages にポリシーを適用するには、認可規約を使用します。
Requirements
認可要件は、ポリシーで現在のユーザー プリンシパルを評価するために使用できるデータ パラメーターのコレクションです。 "AtLeast21" ポリシーの要件は、1 つのパラメーターつまり最低年齢です。 要件は、空のマーカー インターフェイスである IAuthorizationRequirement を実装します。 パラメーター化された最低年齢要件は、次のように実装できます。
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
認可ポリシーに複数の認可要件が含まれている場合、ポリシーの評価が成功するには、すべての要件が合格する必要があります。 つまり、1 つの認可ポリシーに追加された複数の認可要件は、AND ベースで処理されます。
Note
要件には、データまたはプロパティがなくてもかまいません。
認可ハンドラー
認可ハンドラーでは、要件のプロパティの評価が行われます。 承認ハンドラーは、指定されたAuthorizationHandlerContextに対して要件を評価し、accessが許可されているかどうかを判断します。
1 つの要件に複数のハンドラーを指定できます。 ハンドラーは AuthorizationHandler<TRequirement> を継承できます。ここで TRequirement は処理される要件です。 または、ハンドラーで IAuthorizationHandler を実装して、複数の種類の要件を処理することもできます。
1 つの要件にハンドラーを使用する
以下の例は、最低年齢を管理するハンドラーが単一の要件を使用する1対1の関係を示しています。
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com"))
{
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(
context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com").Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
上記のコードにより、現在のユーザー プリンシパルに、既知の信頼された発行者によって発行された生年月日クレームがあるかどうかが判断されます。 クレームが見つからないと認可は行われず、その場合は完了したタスクが返されます。 クレームが存在する場合は、ユーザーの年齢が計算されます。 要件で定義されている最低年齢をユーザーが満たしている場合、認可は成功と見なされます。 認可が成功すると、満たされた要件を唯一のパラメーターとして context.Succeed が呼び出されます。
複数の要件にハンドラーを使用する
次の例は、アクセス許可ハンドラーで 3 種類の異なる要件を処理できる一対多の関係を示したものです。
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class PermissionHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is ReadPermission)
{
if (IsOwner(context.User, context.Resource) ||
IsSponsor(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditPermission ||
requirement is DeletePermission)
{
if (IsOwner(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsOwner(ClaimsPrincipal user, object resource)
{
// Code omitted for brevity
return true;
}
private bool IsSponsor(ClaimsPrincipal user, object resource)
{
// Code omitted for brevity
return true;
}
}
上のコードでは、成功とマークされていない要件が含まれる PendingRequirements プロパティが走査されます。
ReadPermission要件の場合、要求されたリソースをaccessするには、ユーザーが所有者またはスポンサーである必要があります。
EditPermissionまたは DeletePermission 要件の場合、ユーザーは要求されたリソースをaccessする所有者である必要があります。
ハンドラーの登録
ハンドラーは、構成の間にサービス コレクションで登録されます。 例えば次が挙げられます。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}
上のコードでは、MinimumAgeHandler を呼び出すことによって、services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); がシングルトンとして登録されます。 ハンドラーは、組み込みのサービス有効期間のいずれかを使って登録できます。
IAuthorizationRequirement と IAuthorizationHandler の両方を実装する 1 つのクラスに、要件とハンドラーの両方をバンドルすることができます。 これにより、ハンドラーと要件の間に緊密な結合が作成されるので、単純な要件とハンドラーにのみ推奨されます。 両方のインターフェイスを実装するクラスを作成すると、要件で自身を処理できる組み込みの PassThroughAuthorizationHandler があるため、DI にハンドラーを登録する必要がなくなります。
AssertionRequirementが要件であり、完全に自己完結型クラスのハンドラーである良い例については、AssertionRequirement クラスを参照してください。
ハンドラーは何を返すべきか?
Handleにおけるメソッドは値を返さないことに注意してください。 成功または失敗の状態はどのようにして示されるのでしょうか。
ハンドラーは、
context.Succeed(IAuthorizationRequirement requirement)を呼び出し、検証が成功した要件を渡すことによって、成功を示します。同じ要件が他のハンドラーで成功する可能性があるので、一般に、ハンドラーで失敗を処理する必要はありません。
他の要件ハンドラーが成功した場合でも、失敗を保証するには、
context.Failを呼び出します。
あるハンドラーで context.Succeed または context.Fail が呼び出された場合でも、他のすべてのハンドラーが呼び出されます。 これにより、別のハンドラーで検証が成功したり要件が失敗した場合でも発生するログ記録などの副作用を、要件で生成できます。
false に設定すると、InvokeHandlersAfterFailure プロパティにより、context.Fail が呼び出されたときのハンドラーの実行が省略されます。
InvokeHandlersAfterFailure の既定値は true で、すべてのハンドラーが呼び出されます。
Note
認可ハンドラーは、認証が失敗した場合でも呼び出されます。
要件に対して複数のハンドラーが必要な理由
評価を OR ベースで行いたい場合は、1 つの要件に対して複数のハンドラーを実装します。 たとえば、Microsoftには、キー カードでのみ開くドアがあります。 鍵カードを自宅に置いておくと、受付の方が一時ステッカーを印刷してドアを開けます。 このシナリオでは、要件は BuildingEntry の 1 つですが、それぞれが 1 つの要件を調べる複数のハンドラーを使用します。
BuildingEntryRequirement.cs
using Microsoft.AspNetCore.Authorization;
public class BuildingEntryRequirement : IAuthorizationRequirement
{
}
BadgeEntryHandler.cs
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "BadgeId" &&
c.Issuer == "http://microsoftsecurity"))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
TemporaryStickerHandler.cs
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
c.Issuer == "https://microsoftsecurity"))
{
// We'd also check the expiration date on the sticker.
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
両方のハンドラーを登録する必要があります。 ポリシーで BuildingEntryRequirement が評価されるときに、いずれかのハンドラーが成功した場合、ポリシーの評価は成功します。
func を使用してポリシーを満たす
コードで表現してポリシーを簡単に満たすことができる場合があります。
Func<AuthorizationHandlerContext, bool> ポリシー ビルダーでポリシーを構成するときに、RequireAssertion を指定できます。
たとえば、前の BadgeEntryHandler を次のように書き換えることができます。
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
(c.Type == "BadgeId" ||
c.Type == "TemporaryBadgeId") &&
c.Issuer == "https://microsoftsecurity")));
});
ハンドラーで MVC 要求コンテキストをAccessする
認可ハンドラーで実装する HandleRequirementAsync メソッドには、AuthorizationHandlerContext と処理対象の TRequirement の 2 つのパラメーターがあります。 MVC や SignalR などのフレームワークにより、Resource の AuthorizationHandlerContext プロパティに任意のオブジェクトが自由に追加されて、追加の情報が渡されます。
エンドポイント ルーティングを使用している場合、通常、認可は認可ミドルウェアによって処理されます。 この場合、Resource プロパティは HttpContext のインスタンスです。 コンテキストを使用して現在のエンドポイントをaccessできます。これは、ルーティング先の基になるリソースをプローブするために使用できます。 例えば次が挙げられます。
if (context.Resource is HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
...
}
従来のルーティングでは、または MVC の認可フィルターの一部として認可が行われるときは、Resource の値は AuthorizationFilterContext のインスタンスです。 このプロパティは、HttpContext、RouteData、MVC、およびRazorPagesによって提供されるその他すべてのアクセスを提供します。
Resource プロパティの使用はフレームワーク固有です。
Resource プロパティの情報を使用すると、認可ポリシーが特定のフレームワークに制限されます。
Resource キーワードを使って is プロパティをキャストしてから、キャストが成功したことを確認して、他のフレームワークで実行したときにコードが InvalidCastException でクラッシュしないようにする必要があります。
// Requires the following import:
// using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
}
すべてのユーザーの認証をグローバルに要求する
すべてのアプリ ユーザーに認証を要求する方法については、「承認によって保護されたユーザー データを使用して ASP.NET Core アプリを作成するを参照してください。
ASP.NET Core