.net core 自定義授權策略提供程式進行許可權驗證 在這之前先瞭解一下鑒權和授權的概念; 鑒權 鑒權可以說是身份驗證,身份驗證是確定用戶身份的過程; 在ASP.NET Core 中身份驗證是由身份驗證服務IAuthenticationService負責的,它被身份驗證中間件使用, 身份驗證服務會 ...
.net core 自定義授權策略提供程式進行許可權驗證
在這之前先瞭解一下鑒權和授權的概念;
鑒權
鑒權可以說是身份驗證,身份驗證是確定用戶身份的過程;
在ASP.NET Core 中身份驗證是由身份驗證服務IAuthenticationService負責的,它被身份驗證中間件使用, 身份驗證服務會使用已註冊的身份驗證處理程式來完成與身份驗證相關的操作。身份驗證相關的操作包括:對用戶身份進行驗證,對未經身份驗證的用戶進行資源訪問時做出響應。
身份驗證處理程式及其配置選項
身份驗證處理程式包括CookieAuthenticationHandler 和 JwtBearerHandler,身份驗證處理程式的註冊 是在調用AddAuthentication之後擴展方法AddJwtBearer 和 AddCookie 提供的
身份驗證處理程式會由實現IAuthenticationService 介面的AuthenticationService 的AuthenticateAsync 方法去調用
授權
授權是確定用戶是否有權訪問資源的過程,這裡先簡單帶過一下後面接著講
授權方案
授權方案包括 基於角色的授權,基於聲明的授權,基於策略的授權,這裡著重說一下策略授權,
基於策略的授權
授權策略包含一個或多個要求
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
在前面的示例中,創建了“AtLeast21”策略。 該策略有一個最低年齡要求,其作為要求的參數提供
IAuthorizationRequirement
IAuthorizationRequirement用於跟蹤授權是否成功的機制
在IAuthorizationHandler 的 HandleAsync方法中 作為參數被調用,由HttpContext 的Requirements 屬性提供
IAuthorizationHandler
IAuthorizationHandler 用於檢查策略是否滿足要求,主要執行的方法是HandleAsync,我們可以繼承微軟提供的AuthorizationHandler,預設實現了HandlAsync ,在具有多個IAuthorizationRequirement 的情況下預設是迴圈去執行HandleRequirementAsync方法,在某些情況下我們可以去重寫從而去執行特定IAuthorizationRequirement,當然方法多樣
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.Resource is TResource)
{
foreach (var req in context.Requirements.OfType<TRequirement>())
{
await HandleRequirementAsync(context, req, (TResource)context.Resource);
}
}
}
IAuthorizationPolicyProvider
IAuthorizationPolicyProvider 自定義策略提供程式
繼承IAuthorizationPolicyProvider 需要去實現IAuthorizationPolicyProvider 三個方法,三個方法的執行由我們我們的Authorize特性決定,並且Authorize特性的策略名稱會傳遞到 IAuthorizationPolicyProvider 的GetPolicyAsync 作為參數使用
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
var policy = new AuthorizationPolicyBuilder();
//添加鑒權方案
policy.AddAuthenticationSchemes("Bearer");
policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
if (policyName is null)
{
return Task.FromResult<AuthorizationPolicy>(null);
}
var authorizations = policyName.Split(',');
if (authorizations.Any())
{
//許可權策略構建器,添加自定義的AuthorizaRequirement
policy.AddRequirements(new AuthorizeRequirement(authorizations));
}
return Task.FromResult(policy.Build());
}
這裡看一下Authorize特性相關的屬性
public class AuthorizeAttribute : Attribute, IAuthorizeData
{
/// <summary>
/// </summary>
public AuthorizeAttribute() { }
/// <summary>
/// 初始化類類實例並且設置可訪問資源策略名稱
/// </summary>
/// <param name="policy">The name of the policy to require for authorization.</param>
public AuthorizeAttribute(string policy)
{
Policy = policy;
}
/// <summary>
/// 設置或獲取可以訪問資源策略名稱
/// </summary>
public string? Policy { get; set; }
/// <summary>
/// 設置或獲取可以訪問資源的角色
/// </summary>
public string? Roles { get; set; }
/// <summary>
/// 設置或獲取可以訪問資源鑒權方案名稱
/// </summary>
public string? AuthenticationSchemes { get; set; }
}
GetDefaultPolicyAsync
預設策略,當我們的Authorize特性上不提供策略時執行,這裡的CustomAuthorization特性是繼承了Authorize特性
[CustomAuthorization]
[HttpGet("SetNotOP")]
[NoResult]
public int SetNotOP()
{
throw new ArgumentNullException(nameof(TestTask));
return 1;
}
GetPolicyAsync
在Authorize添加策略時執行
[CustomAuthorization("test1", "test1")]
[HttpGet("TestTask")]
public async Task<int> TestTask()
{
await Task.CompletedTask;
return 1;
}
GetFallbackPolicyAsync
後備授權策略,是指在沒有為請求指定其他策略時,由授權中間件提供的策略。在這裡可以指返回空值,也可以設置指定策略返回
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
{
return Task.FromResult<AuthorizationPolicy>(null);
}
//或者
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
{
var policy = new AuthorizationPolicyBuilder();
policy.AddAuthenticationSchemes("Bearer");
policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
return Task.FromResult<AuthorizationPolicy>(policy.Build());
}
IAuthorizationService
IAuthorizationService是確認授權成功與否的主要服務,兵器 負責去執行我們自定義的AuthorizationHandle
看一段由微軟官方簡化授權服務的代碼,可以看到AuthorizeAsync會去迴圈執行自定義的AuthorizationHandle
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);
}
到這裡應該對整個授權流程有了個大致的瞭解,在授權前會由鑒權中間件進行一個鑒權,鑒權通過後由IAuthorizationPolicyProvider 來提供一個授權策略(授權策略里可以添加我們需要的IAuthorizationRequirement),最後由IAuthorizationService 的HandleAsync去執行自定義AuthorizeHandle
具體實現
自定義特性
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
public virtual string[] AuthorizeName { get; private set; }
public CustomAuthorizationAttribute(params string[] authorizeName)
{
AuthorizeName = authorizeName;
Policy = string.Join(",", AuthorizeName);
}
}
自定義策略提供程式
public class AuthorizationProvider : IAuthorizationPolicyProvider
{
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
var policy = new AuthorizationPolicyBuilder();
policy.AddAuthenticationSchemes("Bearer");
policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
//預設授權測率必須添加一個IAuthorizationRequirement的實現
policy.AddRequirements(new AuthorizeRequirement());
return Task.FromResult<AuthorizationPolicy>(policy.Build());
}
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
{
return Task.FromResult<AuthorizationPolicy>(null);
}
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
var policy = new AuthorizationPolicyBuilder();
policy.AddAuthenticationSchemes("Bearer");
policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
if (policyName is null)
{
return Task.FromResult<AuthorizationPolicy>(null);
}
var authorizations = policyName.Split(',');
if (authorizations.Any())
{
policy.AddRequirements(new AuthorizeRequirement(authorizations));
}
return Task.FromResult(policy.Build());
}
}
自定義授權處理程式
IPermissionsCheck 是我註入的許可權檢測程式,其實對於許可權認證,重要的是控制對資源的訪問,整篇文章下來無非就是將特性上的值提供到我們所需要進行許可權檢測的程式中去,當然我們也可以用許可權過濾器反射獲取Authorize特性上的值來實現
public class AuthorizeHandler : AuthorizationHandler<AuthorizeRequirement>
{
private readonly IPermissionCheck _permisscheck;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthorizeHandler(IHttpContextAccessor httpContextAccessor
, IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
_permisscheck = scope.ServiceProvider.GetRequiredService<IPermissionCheck>();
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeRequirement requirement)
{
var identity = _httpContextAccessor?.HttpContext?.User?.Identity;
var httpContext = _httpContextAccessor?.HttpContext;
var isAuthenticated = identity?.IsAuthenticated ?? false;
var claims = _httpContextAccessor?.HttpContext?.User?.Claims;
var userId = claims?.FirstOrDefault(p => p.Type == "Id")?.Value;
//判斷是否通過鑒權中間件--是否登錄
if (userId is null || !isAuthenticated)
{
context.Fail();
return;
}
var defaultPolicy = requirement.AuthorizeName?.Any() ?? false;
//預設授權策略
if (!defaultPolicy)
{
context.Succeed(requirement);
return;
}
var roleIds = claims?
.Where(p => p?.Type?.Equals("RoleIds") ?? false)
.Select(p => long.Parse(p.Value));
var roleNames = claims?
.Where(p => p?.Type?.Equals(ClaimTypes.Role) ?? false)
.Select(p => p.Value);
UserTokenModel tokenModel = new UserTokenModel()
{
UserId = long.Parse(userId ?? "0"),
UserName = claims?.FirstOrDefault(p => p.Type == ClaimTypes.Name)?.Value ?? "",
RoleNames = roleNames?.ToArray(),
RoleIds = roleIds?.ToArray(),
};
if (requirement.AuthorizeName.Any())
{
if (!_permisscheck.IsGranted(tokenModel, requirement.AuthorizeName))
{
context.Fail();
return;
}
}
context.Succeed(requirement);
}
}
自定義IAuthorizationRequirement
public class AuthorizeRequirement : IAuthorizationRequirement
{
public virtual string[] AuthorizeName { get; private set; }
public AuthorizeRequirement(params string[] authorizeName)
{
AuthorizeName = authorizeName;
}
public AuthorizeRequirement() { }
}
自定義授權結果中間件
自定義授權結果中間件的作用,返回自定義響應,增強預設質詢或禁止響應
public class AuthorizeMiddleHandle : IAuthorizationMiddlewareResultHandler
{
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if (!authorizeResult.Succeeded || authorizeResult.Challenged)
{
var isLogin = context?.User?.Identity?.IsAuthenticated ?? false;
var path = context?.Request?.Path ?? "";
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
var response = new AjaxResponse();
response.UnAuthorizedRequest = true;
response.StatusCode = "401";
var error = new ErrorInfo();
error.Error = isLogin ? $"你沒有許可權訪問該介面-介面路由{path}" : "請先登錄系統";
response.Error = error;
await context.Response.WriteAsJsonAsync(response);
return;
}
await next(context);
}
}
相關服務的註冊
context.Services.AddScoped<IPermissionCheck, PermissionCheck>();
context.Services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationProvider>();
context.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizeMiddleHandle>();
context.Services.AddSingleton<IAuthorizationHandler, AuthorizeHandler>();
到這裡對於許可權認證有了個大概的瞭解,至於是通過自定義策略提供程式自定義AuthorizHandle這一系列複雜的操作還是通過許可權過濾器取決看官自己。個人認為通過自定義策略提供程式自定義AuthorizHandle這種方式更靈活性,能夠應對更多複雜場景。