.net core 自定义授权策略提供程序进行权限验证

.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这种方式更灵活性,能够应对更多复杂场景。