艾三元 Every day to be a little better

基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (上篇)

一、前言

回顾:认证授权方案之授权初识

从上一节中,我们在对授权系统已经有了初步的认识和使用,可以发现,asp.net core为我们提供的授权策略是一个非常强大丰富且灵活的认证授权方案,能够满足大部分的授权场景。

在ConfigureServices中配置服务:将授权服务添加到容器

    public void ConfigureServices(IServiceCollection services)
    {
   	  services.AddAuthorization(options =>
            {
                options.AddPolicy("customizePermisson",
                  policy => policy
                    .Requirements
                    .Add(new PermissionRequirement("user")));
            });
            //此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
            services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
    }

在Configure中注册管道:运行使用调用方法来配置Http请求管道

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {    
        //开启授权
        app.UseAuthorization();

    }

通过以上几行代码的实现,就可以进行授权了,这个时候,你可以会问,这几行代码都进行了什么操作实现授权的?

好了,继续回到上节最后说的在这一节中对授权策略的核心进行一步步的揭秘的。

二、开始

引入整体结构

授权

2.1 添加授权AddAuthorization

添加授权策略服务使用AddAuthorization方法,以便调用。

从源码可以发现,从core3.0后,由之前在core2.0中的AuthorizationServiceCollectionExtensions.cs文件中,原来的AddAuthorization的方法变为了AddAuthorizationCore方法,微软在这一块进行了封装在PolicyServiceCollectionExtensions.cs文件中,沿用了之前AddAuthorization拓展名称,不影响之前版本的使用。

我们来看看aspnetcore源码:

    public static class PolicyServiceCollectionExtensions
    {
        public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            services.TryAddSingleton<AuthorizationPolicyMarkerService>();
            services.TryAddTransient<IPolicyEvaluator, PolicyEvaluator>();
            services.TryAddTransient<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
            return services;
        }
        public static IServiceCollection AddAuthorization(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services.AddAuthorizationCore();
            services.AddAuthorizationPolicyEvaluator();
            return services;
        }      
        public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            services.AddAuthorizationCore(configure);
            services.AddAuthorizationPolicyEvaluator();
            return services;
        }
    }
    public static class AuthorizationServiceCollectionExtensions
    {
        public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
            services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
            services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
            services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());           		     services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
            services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
            return services;
        }
        public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            services.Configure(configure);
            return services.AddAuthorizationCore();
        }
    }

由上可知,在调用AddAuthorization方法进行授权配置的时候,需要使用到AuthorizationOptions委托方式传参。

所以我们再来看看下面这一行代码,通过AddPolicy实现添加策略方式。

   options.AddPolicy("customizePermisson",policy => policy.Requirements.Add(new PermissionRequirement("user")));

查看源码发现是引用了AuthorizationOptions对象。

2.2 配置选项AuthorizationOptions

授权选项实现添加和授权配置,提供授权服务的配置。

源码如下:

    public class AuthorizationOptions
    {
        private Dictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
      
        public bool InvokeHandlersAfterFailure { get; set; } = true;
      
        public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
       
        public AuthorizationPolicy? FallbackPolicy { get; set; }

        public void AddPolicy(string name, AuthorizationPolicy policy)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (policy == null)
            {
                throw new ArgumentNullException(nameof(policy));
            }
            PolicyMap[name] = policy;
        }

        public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (configurePolicy == null)
            {
                throw new ArgumentNullException(nameof(configurePolicy));
            }
            var policyBuilder = new AuthorizationPolicyBuilder();
            configurePolicy(policyBuilder);
            PolicyMap[name] = policyBuilder.Build();
        }
      
        public AuthorizationPolicy GetPolicy(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (PolicyMap.TryGetValue(name, out var value))
            {
                return value;
            }
            return null;
        }
    }

定义一个字典

private Dictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);

目的在于将定义的授权策略方式都保存在这个声明的PolicyMap当中,而其中AddPolicy方法是将配置的策略添加到字典中。

public void AddPolicy(string name, AuthorizationPolicy policy);
public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy);

而这方法中涉及到两种不同的传参对象AuthorizationPolicyAuthorizationPolicyBuilder

2.3 授权策略 AuthorizationPolicy

表示授权要求和方案的集合。具体源码如下:

public class AuthorizationPolicy
{
    public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes)
    {
        if (requirements == null)
        {
            throw new ArgumentNullException(nameof(requirements));
        }

        if (authenticationSchemes == null)
        {
            throw new ArgumentNullException(nameof(authenticationSchemes));
        }

        if (requirements.Count() == 0)
        {
            throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
        }
        Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
        AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
    }
  
    public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
    
    public IReadOnlyList<string> AuthenticationSchemes { get; }
   
    public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
    {
        if (policies == null)
        {
            throw new ArgumentNullException(nameof(policies));
        }

        return Combine((IEnumerable<AuthorizationPolicy>)policies);
    }
   
    public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies)
    {
        if (policies == null)
        {
            throw new ArgumentNullException(nameof(policies));
        }

        var builder = new AuthorizationPolicyBuilder();
        foreach (var policy in policies)
        {
            builder.Combine(policy);
        }
        return builder.Build();
    }
   
    public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
    {
        if (policyProvider == null)
        {
            throw new ArgumentNullException(nameof(policyProvider));
        }

        if (authorizeData == null)
        {
            throw new ArgumentNullException(nameof(authorizeData));
        }

        // Avoid allocating enumerator if the data is known to be empty
        var skipEnumeratingData = false;
        if (authorizeData is IList<IAuthorizeData> dataList)
        {
            skipEnumeratingData = dataList.Count == 0;
        }

        AuthorizationPolicyBuilder policyBuilder = null;
        if (!skipEnumeratingData)
        {
            foreach (var authorizeDatum in authorizeData)
            {
                if (policyBuilder == null)
                {
                    policyBuilder = new AuthorizationPolicyBuilder();
                }

                var useDefaultPolicy = true;
                if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
                {
                    var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
                    if (policy == null)
                    {
                        throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
                    }
                    policyBuilder.Combine(policy);
                    useDefaultPolicy = false;
                }

                var rolesSplit = authorizeDatum.Roles?.Split(',');
                if (rolesSplit != null && rolesSplit.Any())
                {
                    var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
                    policyBuilder.RequireRole(trimmedRolesSplit);
                    useDefaultPolicy = false;
                }

                var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
                if (authTypesSplit != null && authTypesSplit.Any())
                {
                    foreach (var authType in authTypesSplit)
                    {
                        if (!string.IsNullOrWhiteSpace(authType))
                        {
                            policyBuilder.AuthenticationSchemes.Add(authType.Trim());
                        }
                    }
                }

                if (useDefaultPolicy)
                {
                    policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
                }
            }
        }

        // If we have no policy by now, use the fallback policy if we have one
        if (policyBuilder == null)
        {
            var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
            if (fallbackPolicy != null)
            {
                return fallbackPolicy;
            }
        }

        return policyBuilder?.Build();
    }
}

我们从源码中可以发现,Authorization 对象 Combine方法目的在于将授权策略进行合并,同时调用了AuthorizationPolicyBuilder对象中Combine方法,对授权方案或者授权策略进行合并。再来看看AuthorizationPolicy对象中的CombineAsync方法,这里的参数用到了IAuthorizeData,同时这个方法的过程是将可能基于角色,基于方案或者基于策略都合并转换为是授权策略的方式,也是通过调用AuthorizationPolicyBuilder对象来实现合并。 所以可以看得出AuthorizationPolicyBuilder提供了一些创建AuthorizationPolicy的方法。

这个时候,我们可以发现,其实之前说的基于角色、基于方案的授权方式本质上来说都是基于策略授权。

2.4 构建策略AuthorizationPolicyBuilder

除了上面说到使用AuthorizationPolicy对象之外,我们还可以用AuthorizationPolicyBuilder对象以Buider来创建AuthorizationPolicy对象,将多个AuthorizationPolicy对象提供的数组进行合并,所以AuthorizationPolicyBuilder提供的Combine方法的使用,为AuthorizationPolicy授权构建提供了许多便捷的方式。

public class AuthorizationPolicyBuilder
{ 
    public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
    {
        AddAuthenticationSchemes(authenticationSchemes);
    }
    public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
    {
        Combine(policy);
    }
    public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();    
    public IList<string> AuthenticationSchemes { get; set; } = new List<string>();
    public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
    {
        foreach (var authType in schemes)
        {
            AuthenticationSchemes.Add(authType);
        }
        return this;
    }    
    public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements)
    {
        foreach (var req in requirements)
        {
            Requirements.Add(req);
        }
        return this;
    }
    public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
    {
        if (policy == null)
        {
            throw new ArgumentNullException(nameof(policy));
        }

        AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
        AddRequirements(policy.Requirements.ToArray());
        return this;
    }    
    public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        return RequireClaim(claimType, (IEnumerable<string>)allowedValues);
    } 
    public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> allowedValues)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
        return this;
    }    
    public AuthorizationPolicyBuilder RequireClaim(string claimType)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues: null));
        return this;
    }     
    public AuthorizationPolicyBuilder RequireRole(params string[] roles)
    {
        if (roles == null)
        {
            throw new ArgumentNullException(nameof(roles));
        }

        return RequireRole((IEnumerable<string>)roles);
    }  
    public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)
    {
        if (roles == null)
        {
            throw new ArgumentNullException(nameof(roles));
        }

        Requirements.Add(new RolesAuthorizationRequirement(roles));
        return this;
    }    
    public AuthorizationPolicyBuilder RequireUserName(string userName)
    {
        if (userName == null)
        {
            throw new ArgumentNullException(nameof(userName));
        }

        Requirements.Add(new NameAuthorizationRequirement(userName));
        return this;
    }
    public AuthorizationPolicyBuilder RequireAuthenticatedUser()
    {
        Requirements.Add(new DenyAnonymousAuthorizationRequirement());
        return this;
    }
    public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, bool> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Requirements.Add(new AssertionRequirement(handler));
        return this;
    }
    public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Requirements.Add(new AssertionRequirement(handler));
        return this;
    } 
    public AuthorizationPolicy Build()
    {
        return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
    }
}

由上面多出出现的IAuthorizationRequirement对象可以发现,授权要求Requirement属性是策略的核心方案,每一种Requirement都代表一种授权方式。同时IAuthorizationPolicyBuilder为这些预定义的方案创建了它们对应的使用方式并将其添加到Requirements集合中。

2.5 授权要求IAuthorizationRequirement

   public interface IAuthorizationRequirement
   {
   }

接口并没有任何实现成员,因为授权要求是具有不同的表现形式的,所有才没有具体的实现成员。授权要求目的在于检验某个当前用户是否具有相应的要求, 所以大部分IAuthorizationRequirement接口的实现类都继承了IAuthorizationHandler 接口来提供HandleAsync方法来实现对应的授权检验。

下面介绍asp.net core框架里面默认实现的几种IAuthorizationRequirement实现类型。

2.5.1 DenyAnonymousAuthorizationRequirement

阻止匿名用户操作,言外之意就是拒绝未被验证的匿名用户访问资源。

源码如下:

public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
    {
        var user = context.User;
        var userIsAnonymous =
            user?.Identity == null ||
            !user.Identities.Any(i => i.IsAuthenticated);
        if (!userIsAnonymous)
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

通过用户的CliamPrincipal对象身份是否为空或是否是一个经过认证的用户身份,以此来确定当前请求的用户是否来源于匿名用户。

2.5.2 NameAuthorizationRequirement

指定用户名的授权方式,判断当前用户与某个指定的用户是否匹配以此来授权访问资源。

源码如下:

public class NameAuthorizationRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement
{
    public NameAuthorizationRequirement(string requiredName)
    {
        if (requiredName == null)
        {
            throw new ArgumentNullException(nameof(requiredName));
        }

        RequiredName = requiredName;
    }
    public string RequiredName { get; }
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement)
    {
        if (context.User != null)
        {
            if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName)))
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }
}

其中RequiredName属性为授权用户,通过HandleRequirementAsync方法进行校验当前用户的ClaimPrincipal对象的身份与RequiredName是否具有匹配。

这里的判断用的是 string.Equals() 说明这里比较的用户名是区别大小写的。

2.5.3 ClaimsAuthorizationRequirement

基于指定声明类型的授权策略,检验当前用户是否声明类型和候选值。

源码如下:

public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
    {
        public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string> allowedValues)
        {
            if (claimType == null)
            {
                throw new ArgumentNullException(nameof(claimType));
            }
            ClaimType = claimType;
            AllowedValues = allowedValues;
        }
        public string ClaimType { get; }
        public IEnumerable<string> AllowedValues { get; }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsAuthorizationRequirement requirement)
        {
            if (context.User != null)
            {
                var found = false;
                if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
                {
                    found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
                }
                else
                {
                    found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
                                                        && requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
                }
                if (found)
                {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
        }
    }

由上我们可以看的出,ClaimTypeAllowedValues这两个属性在构造函数中被初始化,分别用来表示当前声明的声明类型和默认允许值。通过HandleRequirementAsync来授权检验是否完成通过。

2.5.4 RolesAuthorizationRequirement

基于角色的授权策略,检验当前用户是否拥有约定匹配的角色,如果拥有,则可以访问对应的资源。

源码如下:

public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
    public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
    {
        if (allowedRoles == null)
        {
            throw new ArgumentNullException(nameof(allowedRoles));
        }

        if (allowedRoles.Count() == 0)
        {
            throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
        }
        AllowedRoles = allowedRoles;
    }
    public IEnumerable<string> AllowedRoles { get; }
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
    {
        if (context.User != null)
        {
            bool found = false;
            if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
            {
                // Review: What do we want to do here?  No roles requested is auto success?
            }
            else
            {
                found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
            }
            if (found)
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }
}

其中AllowedRoles表示目标角色列表的集合。通过HandleRequirementAsync实现授权检验,调用IsInRole方法来判断当前用户的ClaimsPrincipal对象是否有指定的角色。

2.5.5 AssertionRequirement

基于AuthorizationHandlerContext上下文断言的形式来声明授权。

源码如下:

public class AssertionRequirement : IAuthorizationHandler, IAuthorizationRequirement
{
    public Func<AuthorizationHandlerContext, Task<bool>> Handler { get; }
    public AssertionRequirement(Func<AuthorizationHandlerContext, bool> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Handler = context => Task.FromResult(handler(context));
    }
    public AssertionRequirement(Func<AuthorizationHandlerContext, Task<bool>> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Handler = handler;
    }
    public async Task HandleAsync(AuthorizationHandlerContext context)
    {
        if (await Handler(context))
        {
            context.Succeed(this);
        }
    }
}

通过类型为Func<AuthorizationHandlerContext, Task<bool>>的委托来表示该断言,利用它来授权验证。在HandleAsync检验方法中,直接调用这个委托对象来完成判断。

2.5.6 OperationAuthorizationRequirement

基于预定义操作的授权策略。

源码如下:

public class OperationAuthorizationRequirement : IAuthorizationRequirement
{
    public string Name { get; set; }
}

由上可知,只是包含一个操作名字的Name属性,目的在于将授权的目标对象映射到一个预定义的操作上。

三、用例

出现的IAuthorizationRequirement对象可以发现,授权要求Requirement属性是策略的核心方案,每一中Requirement都代表一种授权方式。

在上文我们通过构建策略AuthorizationPolicyBuilder对象的源码可以发现,为我们提供了多个方法由预定义的IAuthorizationRequirement类型来创建并将其添加到Requirements集合中。

3.1 应用

实例应用如下:在ConfigureServices中配置服务中

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            var combindPolicy = new AuthorizationPolicyBuilder().RequireClaim("role").Build();
            services.AddAuthorization(options =>
            {
                //DenyAnonymousAuthorizationRequirement
                options.AddPolicy("DenyAnonyUser", policy => policy.RequireAuthenticatedUser());
                //NameAuthorizationRequirement
                options.AddPolicy("NameAuth", policy => policy.RequireUserName("艾三元"));
                //ClaimsAuthorizationRequirement
                options.AddPolicy("ClaimsAuth", policy => policy.RequireClaim("role","admin"));
                //RolesAuthorizationRequirement
                options.AddPolicy("RolesAuth", policy => policy.RequireRole("admin","user"));
                //AssertionRequirement
                options.AddPolicy("AssertAuth", policy => policy.RequireAssertion(c=>c.User.HasClaim(o=>o.Type=="role")));
                //同样可可用直接调用Combind方法,策略AuthorizationPolicy
                options.AddPolicy("CombindAuth", policy => policy.Combine(combindPolicy));
            });
}

以上,分别实现了框架中默认实现的几种IAuthorizationRequirement实现类型在实际中的应用,通过不同授权要求实现的策略方式,同时也可以将上面多种方式合并成一个对象,进行调用使用。

3.2 拓展

当然了,除了自带了这几种默认实现方式之外,我们也可以通过自定义Requirement来满足我们的需求。

这个在上一节初识授权的时候,已经提到了自定义授权这一块,所以在这里再看一次。

定义一个权限策略PermissionRequirement,这个策略并包含一些属性。

public class PermissionRequirement: IAuthorizationRequirement
{
    public string _permissionName { get; }

    public PermissionRequirement(string PermissionName)
    {
        _permissionName = PermissionName;
    }
}

再定义一个策略处理类

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role);
        if (role != null)
        {
            var roleValue = role.Value;
            if (roleValue==requirement._permissionName)
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;

配置使用

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        //基于自定义策略授权
        services.AddAuthorization(options =>
        {
            options.AddPolicy("customizePermisson",
              policy => policy
                .Requirements
                .Add(new PermissionRequirement("admin")));
        });
        //此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
        services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
        // 如前所述,要求可包含多个处理程序。如果为授权层的同一要求向 DI 系统注册多个处理程序,有一个成功就足够了。
    }

特别说明

上述使用的处理程序是一对一的关系,当声明要求满足条件的时候,则任务授权成功, 授权成功后, context.Succeed 将通过满足要求作为其唯一参数调用。

但是授权策略中也包含一对多的要求关系,它们属于 & 的关系,只用全部验证通过,才能最终授权成功。但是在有些场景下,我们可能希望一个授权策略可以适用多种情况,比如,我们进入公司时需要出示员工卡才可以被授权进入,但是如果我们忘了带员工卡,可以去申请一个临时卡,同样可以授权成功。

这里贴一个官方文档的写法:

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}
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;
    }
}
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;
    }
}

我们定义了两个Handler,但是想让它们得到执行,还需要将其注册到DI系统中:

services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();

确保两个处理程序都已注册。 如果某个处理程序在某一策略评估后使用context.succeed()来成功 BuildingEntryRequirement ,则策略评估将成功。但是当我们调用context.Fail()方法后会将授权结构设置失败,那样的话,最后的结果都是会授权失败的。所以正常情况下。我们都是只设置标记context.succeed()

四、说明

这里对上文源码中出现的一些声明方法进行说明。

4.1 IAuthorizeData

使用 IAuthorizeDate 接口方法。定义授权规则应用于资源所需的数据集。

public interface IAuthorizeData
{
    string Policy { get; set; }
    string Roles { get; set; }
    string AuthenticationSchemes { get; set; }
}

Policy:获取或设置确定对资源的访问的策略名称。

Roles: 获取或设置以逗号分隔的允许访问资源的角色列表。

AuthenticationSchemes: 获取或以设置以逗号分隔的方案列表,从中可以构造用户信息。

所以IAuthorizeData中定义的policyrolesAuthenticationSchemes三个分别代表着授权系统中的三种授权方式。

具体的使用在后续讲解授权的执行流程中会进行详细介绍。

五、后续

上面主要讲解了授权在配置方面的源码,本来打算继续接着往下写的,但是考虑到整体篇幅可能会太长了,不便于阅读。

所以授权揭秘的上篇内容就说到这里了,在后续的文章中,会继续深入了解授权内部机制的奥秘以及是如何实现执行授权流程的。

六、总结

  1. 从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构建不同的策略方式,从而实现一种自己满意的授权需求配置要求。
  2. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
  3. 参考的文档 和官方源码
posted @ 2020-07-05 17:45  艾三元  阅读(7071)  评论(7编辑  收藏  举报