.NET Core Identity Policy Customize
在上一篇文章中學習了何謂 Claim
與 Policy
會員在新增完 Claim
之後可以利用我們事先註冊好的 Policy
能夠避免無權限的使用者呼叫機密的 API
但是在進階的場景,例如:年齡限制或者檢查使用者的性別等需要另外對 Claim
內容進行分辨的邏輯,就需要建立客製化的 Policy
要客製化 Policy
我們先看看官方的範例 Github
在 Authorization
有建立了四個 Class
MinimumAgeAuthorizationHandler.cs
MinimumAgeAuthorizeAttribute.cs
MinimumAgePolicyProvider.cs
MinimumAgeRequirement.cs
並且註冊了 MinimumAgePolicyProvider
和 MinimumAgeAuthorizationHandler
生命週期為 Singleton
AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
根據註釋的內容可以得知註冊 MinimumAgePolicyProvider
是用來取代預設注入的 DefaultAuthorizationPolicyProvider
PolicyProvider
會根據傳入的 Policy
名稱註冊相對應的 Policy
到系統內
MinimumAgeAuthorizationHandler
則是負責處理此次授權請求為成功或失敗,並且繼承 AuthorizationHandler<MinimumAgeRequirement>
當有 API 需要檢查 Policy
有要求檢查 MinimumAgeRequirement
就會來調用 MinimumAgeAuthorizationHandler
MinimumAgeAuthorizeAttribute
則是用來設定客製化的授權標籤,可有可無
接下來我們可以試著建立自己的 Policy
mkdir Permissions && cd $_
touch MinimumAgeAuthorizationHandler.cs
touch MinimumAgePolicyProvider.cs
touch MinimumAgeRequirement.cs
照流程來看首先 MinimumAgePolicyProvider
會收到一個 PolicyName
我們需要註冊一個 Policy
到系統內,我們在上一篇文章有學到如何註冊 Policy
//Program.cs
builder.Services.AddAuthorization(option =>
{
option.AddPolicy("birthday", builder =>
{
builder
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme)
.RequireClaim(ClaimTypes.DateOfBirth);
});
});
我們也可以手動進行註冊不需要在額外建立 MinimumAgePolicyProvider
,這邊我們直接建立一個 Policy
名稱為 MinimumAge18
這邊呼叫 AddRequirements
方法新增一個 MinimumAgeRequirement
傳入一個 Int
我們之後要建立的 MinimumAgeAuthorizationHandler
會接收這個 Requirements
之後進行處理
//Program.cs
option.AddPolicy("MinimumAge18", builder =>
{
builder.AddRequirements(new MinimumAgeRequirement(18));
});
這裡我們繼承 AuthorizationHandler<MinimumAgeRequirement>
並且透過 Context
讀出目前登入 User
的 Claim
將 Claim
轉換為使用這目前的年紀,最後在拿使用者的年紀與 MinimumAgeRequirement
的 Int
進行比較
//MinimumAgeAuthorizationHandler.cs
public class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
return Task.FromResult(0);
}
DateTime dateOfBirth =Convert.ToDateTime(context.User?.FindFirst(c => c.Type == ClaimTypes.DateOfBirth)?.Value);
int age = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-age)) age--;
if (age > requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.FromResult(0);
}
}
//Program.cs
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
這樣基本上就可以用了我們到 WeatherForecastController
進行測試
[Authorize(Policy = "MinimumAge18")]
public class WeatherForecastController : ControllerBase
我們明確指定說 Policy
要使用 MinimumAge18
,使用者需要大於 18 歲 API 才會回傳資料
不過這樣使用起來體驗不是很好,我們另外建立 MinimumAgePolicyProvider
與 MinimumAgeAuthorizeAttribute
使用起來會比較方便
這裡建立了一個新標籤可以傳入一個 Int
因為繼承 AuthorizeAttribute
所以我們可以簡化寫法
原本是[Authorize(Policy = "MinimumAge18")]
現在可以改成 [MinimumAgeAuthorize(18)]
可讀性比較高
//MinimumAgeAuthorizeAttribute.cs
public class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
private const string POLICY_PREFIX = "MinimumAge";
public MinimumAgeAuthorizeAttribute(int age)
{
Age = age;
}
public int Age
{
get
{
if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
{
return age;
}
return default;
}
set
{
Policy = $"{POLICY_PREFIX}{value.ToString()}";
}
}
}
根據 MinimumAgeAuthorizeAttribute
的內容,我們會跟系統要求一個 MinimumAge18
的 Policy
MinimumAgePolicyProvider
會將數字 18 分離出來,最後添加MinimumAgeRequirement(18)
//MinimumAgePolicyProvider.cs
internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "MinimumAge";
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
{
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
{
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new MinimumAgeRequirement(age));
return Task.FromResult(policy.Build());
}
return Task.FromResult<AuthorizationPolicy>(null);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => Task.FromResult<AuthorizationPolicy?>(null);
}
//Program.cs
builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
最後我們到 WeatherForecastController
改用我們剛剛建立的標籤,意思明確許多並且也能達到同樣的結果
[MinimumAgeAuthorize(18)]
public class WeatherForecastController : ControllerBase
Summary
今天學習了如何客製化自己的授權邏輯,在某些框架中的授權系統也是使用相同的邏輯來建立出一套複雜的授權系統
今天的進度 Github