基於.NetCore3.1系列 —— 認證授權方案之授權揭秘 (上篇)

来源:https://www.cnblogs.com/i3yuan/archive/2020/07/05/13236777.html

一、前言 回顧:認證授權方案之授權初識 從上一節中,我們在對授權系統已經有了初步的認識和使用,可以發現,asp.net core為我們提供的授權策略是一個非常強大豐富且靈活的認證授權方案,能夠滿足大部分的授權場景。 在ConfigureServices中配置服務:將授權服務添加到容器 public ...


一、前言

回顧:認證授權方案之授權初識

從上一節中,我們在對授權系統已經有了初步的認識和使用,可以發現,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. 參考的文檔 和官方源碼

您的分享是我們最大的動力!

更多相關文章
  • 棧 1. 存放局部變數 2. 不可以被多個線程共用 3. 空間連續,速度快 堆 1. 存放對象 2. 可以被多個線程共用 3. 空間不連續,速度慢。但是靈活 方法區 1. 存放類的信息:代碼、靜態變數、字元串常量等 2. 可以被多個線程共用 3. 空間不連續,速度慢。但是靈活 ...
  • 1 前言 Docker是容器開發的事實標準,而Springboot是Java微服務常用框架,二者必然是會走到一起的。本文將講解如何開發Springboot項目,把它做成Docker鏡像,並運行起來。 2 把Springboot打包成Docker鏡像 Springboot的Web開發非常簡單,本次使用 ...
  • 方法重載(Overload): 兩同(同一個類、同一個方法名)三不同(參數列表不同:;類型、個數、順序) 返回值不同,構成重載嗎? No 形參名稱不同,構成重載嗎? No 方法重寫(0verride): 子類定義一個和父類方法名,參數完全相同的方法。 =: 方法名保持一致 >=: 子類許可權修飾符可以 ...
  • 上篇文章提到FFmpeg解決項目中視頻和語音問題,說道C#和FFmpeg不得不提的2個類庫。1、Xabe.FFmpeg簡單查看了下源碼和demo,發現基於ffmpeg.exe的命令行參數進行處理。2、FFmpeg.AutoGen 把C語言對外API和類型翻譯成C#的API和對象。看了一下這個源碼生成... ...
  • 只讀成員 private struct Point { public Point(double x, double y) { X = x; Y = y; } private double X { get; set; } private double Y { get; set; } private r ...
  • 在斷斷續續的瞭解c#中,最先想到的就是ASP.NET開發了,在C2中應用也慢慢開始比較廣泛了,比如Covenant,有跨平臺的特性,值得看一看。 ...
  • 基礎概念 Microsoft中間語言(MSIL),也成為通用中間語言(CIL),是一組與平臺無關的指令,由特定於語言的編譯器從源代碼生成。MSIL是獨立於平臺的,因此,他可以在任何公共語言基礎架構支持特定的環境上執行。 通過JIT編譯器將MSIL轉換為特定電腦環境的特定機器代碼。這是在執行MSIL ...
  • 項目背景: 工作之餘兼職一家公司(方向是工業4.0)給做IM系統,主要功能包括:文字、 圖片、文件傳輸、遠程協助、視頻語音等等。這些功能都是基於群會話, 比如工廠操作工人遇到問題,請求遠程專家,這個初級專家不能解決問題,會邀請一個高級專家進來解決。開發過程中主要遇到的問題是視頻和語音這一塊,像其他的... ...
一周排行
  • 比如要拆分“呵呵呵90909086676喝喝999”,下麵當type=0返回的是中文字元串“呵呵呵,喝喝”,type=1返回的是數字字元串“90909086676,999”, private string GetStrings(string str,int type=0) { IList<strin ...
  • Swagger一個優秀的Api介面文檔生成工具。Swagger可以可以動態生成Api介面文檔,有效的降低前後端人員關於Api介面的溝通成本,促進項目高效開發。 1、使用NuGet安裝最新的包:Swashbuckle.AspNetCore。 2、編輯項目文件(NetCoreTemplate.Web.c ...
  • 2020 年 7 月 30 日, 由.NET基金會和微軟 將舉辦一個線上和為期一天的活動,包括 微軟 .NET 團隊的演講者以及社區的演講者。本次線上大會 專註.NET框架構建微服務,演講者分享構建和部署雲原生應用程式的最佳實踐、模式、提示和技巧。有關更多信息和隨時瞭解情況:https://focu... ...
  • #abp框架Excel導出——基於vue #1.技術棧 ##1.1 前端採用vue,官方提供 UI套件用的是iview ##1.2 後臺是abp——aspnetboilerplate 即abp v1,https://github.com/aspnetboilerplate/aspnetboilerp ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 作者:碧茂大數據 PS:如有需要Python學習資料的小伙伴可以加下方的群去找免費管理員領取 input()輸入 Python提供了 input() 內置函數從標準輸入讀入一 ...
  • 從12年到20年,python以肉眼可見的趨勢超過了java,成為了當今It界人人皆知的編程語言。 python為什麼這麼火? 網路編程語言搜索指數 適合初學者 Python具有語法簡單、語句清晰的特點,這就讓初學者在學習階段可以把精力集中在編程對象和思維方法上。 大佬都在用 Google,YouT ...
  • 在社會上存在一種普遍的對培訓機構的學生一種歧視的現象,具體表現在,比如:當你去公司面試的時候,一旦你說了你是培訓機構出來的,那麼基本上你就涼了,那麼你瞞著不說,然後又通過了面試成功入職,但是以後一旦在公司被髮現有培訓經歷,可能會面臨被降薪,甚至被辭退,培訓機構出來的學生,在用人單位眼裡就是能力低下的 ...
  • from typing import List# 這道題看了大佬寫的代碼,經過自己的理解寫出來了。# 從最外圍的四周找有沒有為O的,如果有的話就進入深搜函數,然後深搜遍歷# 判斷上下左右的位置是否為Oclass Solution: def solve(self, board: List[List[s ...
  • import requests; import re; import os; # 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, li ...
  • import requests; import re; import os; import parsel; 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537. ...