ASP.NET Core策略受權和 ABP 受權

Github 倉庫源碼地址 https://github.com/whuanles/2020-07-12git

ASP.NET Core 中的策略受權

首先咱們來建立一個 WebAPI 應用。github

而後引入 Microsoft.AspNetCore.Authentication.JwtBearer 包。數據庫

策略

Startup 類的 ConfigureServices 方法中,添加一個策略的形式以下:api

services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

這裏咱們分步來講。app

services.AddAuthorization 用於添加受權方式,目前只支持 AddPolicy。async

ASP.NET Core 中,有基於角色、聲明、策略的三種受權形式,都是使用 AddPolicy 來添加受權處理。ide

其中,有兩個 API 以下:測試

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

name = "AtLeast21",這裏 "AtLeast21" 是策略的名稱。ui

policy.Requirements.Add() 用於添加一個策略的標記(存儲此策略的數據),此標記須要繼承 IAuthorizationRequirement 接口。加密

策略的名稱應該如何設置呢?在受權上應該如何編寫策略以及使用 Requirements.Add()

這裏先放一放,咱們接下來再講解。

定義一個 Controller

咱們來添加一個 Controller :

[ApiController]
    [Route("[controller]")]
    public class BookController : ControllerBase
    {
        private static List<string> BookContent = new List<string>();
        [HttpGet("Add")]
        public string AddContent(string body)
        {
            BookContent.Add(body);
            return "success";
        }

        [HttpGet("Remove")]
        public string RemoveContent(int n)
        {
            BookContent.Remove(BookContent[n]);
            return "success";
        }

        [HttpGet("Select")]
        public List<object> SelectContent()
        {
            List<object> obj = new List<object>();
            int i = 0;
            foreach (var item in BookContent)
            {
                int tmp = i;
                i++;
                obj.Add(new { Num = tmp, Body = item });
            }
            return obj;
        }

        [HttpGet("Update")]
        public string UpdateContent(int n, string body)
        {
            BookContent[n] = body;
            return "success";
        }
    }

功能很簡單,就是對列表內容增刪查改。

設定權限

前面咱們建立了 BookController ,具備增刪查改的功能。應該爲每個功能都應該設置一種權限。

ASP.NET Core 中,一個權限標記,須要繼承IAuthorizationRequirement 接口。

咱們來設置五個權限:

添加一個文件,填寫如下代碼。

/*
     IAuthorizationRequirement 是一個空接口,具體對於受權的需求,其屬性等信息是自定義的
     這裏的繼承關係也沒有任何意義
     */

    // 可以訪問 Book 的權限
    public class BookRequirment : IAuthorizationRequirement
    {
    }

    // 增刪查改 Book 權限
    // 能夠繼承 IAuthorizationRequirement ,也能夠繼承 BookRequirment
    public class BookAddRequirment : BookRequirment
    {
    }
    public class BookRemoveRequirment : BookRequirment
    {
    }
    public class BookSelectRequirment : BookRequirment
    {
    }
    public class BookUpdateRequirment : BookRequirment
    {
    }

BookRequirment 表明可以訪問 BookController,其它四個分別表明增刪查改的權限。

定義策略

權限設定後,咱們開始設置策略。

在 Startup 的 ConfigureServices 中,添加:

services.AddAuthorization(options =>
            {
                options.AddPolicy("Book", policy =>
                {
                    policy.Requirements.Add(new BookRequirment());
                });

                options.AddPolicy("Book:Add", policy =>
                {
                    policy.Requirements.Add(new BookAddRequirment());
                });

                options.AddPolicy("Book:Remove", policy =>
                {
                    policy.Requirements.Add(new BookRemoveRequirment());
                });

                options.AddPolicy("Book:Select", policy =>
                {
                    policy.Requirements.Add(new BookSelectRequirment());
                });

                options.AddPolicy("Book:Update", policy =>
                {
                    policy.Requirements.Add(new BookUpdateRequirment());
                });

            });

這裏咱們爲每種策略只設置一種權限,固然每種策略均可以添加多個權限,

這裏名稱使用 : 隔開,主要是爲了可讀性,讓人一看就知道是層次關係。

存儲用戶信息

這裏爲了更加簡單,就不使用數據庫了。

如下用戶信息結構是隨便寫的。用戶-角色-角色具備的權限。

這個權限用什麼類型存儲均可以。只要可以標識區分是哪一個權限就行。

/// <summary>
    /// 存儲用戶信息
    /// </summary>
    public static class UsersData
    {
        public static readonly List<User> Users = new List<User>();
        static UsersData()
        {
            // 添加一個管理員
            Users.Add(new User
            {
                Name = "admin",
                Email = "admin@admin.com",
                Role = new Role
                {
                    Requirements = new List<Type>
                    {
                        typeof( BookRequirment),
                        typeof( BookAddRequirment),
                        typeof( BookRemoveRequirment),
                        typeof( BookSelectRequirment),
                        typeof( BookUpdateRequirment)
                    }
                }
            });

            // 沒有刪除權限
            Users.Add(new User
            {
                Name = "做者",
                Email = "wirter",
                Role = new Role
                {
                    Requirements = new List<Type>
                    {
                        typeof( BookRequirment),
                        typeof( BookAddRequirment),
                        typeof( BookRemoveRequirment),
                        typeof( BookSelectRequirment),
                    }
                }
            });
        }
    }

    public class User
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public Role Role { get; set; }
    }

    /// <summary>
    /// 這裏的存儲角色的策略受權,字符串數字等都行,只要可以存儲表示就OK
    /// <para>在這裏沒有任何意義,只是標識的一種方式</param>
    /// </summary>
    public class Role
    {
        public List<Type> Requirements { get; set; }
    }

標記訪問權限

定義策略完畢後,就要爲 Controller 和 Action 標記訪問權限了。

使用 [Authorize(Policy = "{string}")] 特性和屬性來設置訪問此 Controller 、 Action 所須要的權限。

這裏咱們分開設置,每一個功能標記一種權限(最小粒度應該是一個功能 ,而不是一個 API)。

[Authorize(Policy = "Book")]
    [ApiController]
    [Route("[controller]")]
    public class BookController : ControllerBase
    {
        private static List<string> BookContent = new List<string>();

        [Authorize(Policy = "Book:Add")]
        [HttpGet("Add")]
        public string AddContent(string body){}

        [Authorize(Policy = "Book:Remove")]
        [HttpGet("Remove")]
        public string RemoveContent(int n){}

        [Authorize(Policy = "Book:Select")]
        [HttpGet("Select")]
        public List<object> SelectContent(){}

        [Authorize(Policy = "Book:Update")]
        [HttpGet("Update")]
        public string UpdateContent(int n, string body){}
    }

認證:Token 憑據

由於使用的是 WebAPI,因此使用 Bearer Token 認證,固然使用 Cookie 等也能夠。使用什麼認證方式均可以。

// 設置驗證方式爲 Bearer Token
            // 添加 using Microsoft.AspNetCore.Authentication.JwtBearer;
            // 你也可使用 字符串 "Brearer" 代替 JwtBearerDefaults.AuthenticationScheme
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234")),    // 加密解密Token的密鑰

                        // 是否驗證發佈者
                        ValidateIssuer = true,
                        // 發佈者名稱
                        ValidIssuer = "server",

                        // 是否驗證訂閱者
                        // 訂閱者名稱
                        ValidateAudience = true,
                        ValidAudience = "client007",

                        // 是否驗證令牌有效期
                        ValidateLifetime = true,
                        // 每次頒發令牌,令牌有效時間
                        ClockSkew = TimeSpan.FromMinutes(120)
                    };
                });

上面的代碼是一個模板,能夠隨便改。這裏的認證方式跟咱們的策略受權沒什麼關係。

頒發登陸憑據

下面這個 Action 放置到 BookController,做爲登陸功能。這一部分也不重要,主要是爲用戶頒發憑據,以及標識用戶。用戶的 Claim 能夠存儲此用戶的惟一標識。

/// <summary>
        /// 用戶登陸而且頒發憑據
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet("Token")]
        public string Token(string name)
        {
            User user = UsersData.Users.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (user is null)
                return "未找到此用戶";

            // 定義用戶信息
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, name),
                new Claim(JwtRegisteredClaimNames.Email, user.Email)
            };

            // 和 Startup 中的配置一致
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234"));

            JwtSecurityToken token = new JwtSecurityToken(
                issuer: "server",
                audience: "client007",
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

            string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
            return jwtToken;
        }

Configure 中補充如下兩行:

app.UseAuthentication();
            app.UseAuthorization();

自定義受權

自定義受權須要繼承 IAuthorizationHandler 接口,實現此接口的類可以決定是否對用戶的訪問進行受權。

實現代碼以下:

/// <summary>
    /// 判斷用戶是否具備權限
    /// </summary>
    public class PermissionHandler : IAuthorizationHandler
    {
        public async Task HandleAsync(AuthorizationHandlerContext context)
        {
            // 當前訪問 Controller/Action 所須要的權限(策略受權)
            IAuthorizationRequirement[] pendingRequirements = context.PendingRequirements.ToArray();

            // 取出用戶信息
            IEnumerable<Claim> claims = context.User?.Claims;

            // 未登陸或者取不到用戶信息
            if (claims is null)
            {
                context.Fail();
                return;
            }


            // 取出用戶名
            Claim userName = claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
            if (userName is null)
            {
                context.Fail();
                return;
            }
            // ... 省略一些檢驗過程 ...

            // 獲取此用戶的信息
            User user = UsersData.Users.FirstOrDefault(x => x.Name.Equals(userName.Value, StringComparison.OrdinalIgnoreCase));
            List<Type> auths = user.Role.Requirements;

            // 逐個檢查
            foreach (IAuthorizationRequirement requirement in pendingRequirements)
            {
                // 若是用戶權限列表中沒有找到此權限的話
                if (!auths.Any(x => x == requirement.GetType()))
                    context.Fail();

                context.Succeed(requirement);
            }

            await Task.CompletedTask;
        }
    }

過程:

  • 從上下文(Context) 中獲取用戶信息(context.User)
  • 獲取此用戶所屬的角色,並獲取此角色具備的權限
  • 獲取這次請求的 Controller/Action 須要的權限(context.PendingRequirements)
  • 檢查所須要的權限(foreach循環),此用戶是否都具備

最後須要將此接口、服務,註冊到容器中:

services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

作完這些後,就能夠測試受權了。

IAuthorizationService

前面實現了 IAuthorizationHandler 接口的類,用於自定義肯定用戶是否有權訪問此 Controller/Action。

IAuthorizationService 接口用於肯定受權是否成功,其定義以下:

public interface IAuthorizationService
    {
        Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement> requirements);

        Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName);
    }

DefaultAuthorizationService 接口實現了 IAuthorizationService ,ASP.NET Core 默認使用 DefaultAuthorizationService 來確認受權。

前面咱們使用 IAuthorizationHandler 接口來自定義受權,若是再深刻一層的話,就追溯到了IAuthorizationService

DefaultAuthorizationService IAuthorizationService 的默認實現,其中有一段代碼以下:

DefaultAuthorizationService 比較複雜,通常狀況下,咱們只要實現 IAuthorizationHandler ` 就夠了。

參考資料:https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.authorization.defaultauthorizationservice?view=aspnetcore-3.1

ABP 受權

前面已經介紹了 ASP.NET Core 中的策略受權,這裏介紹一下 ABP 中的受權,咱們繼續利用前面已經實現的 ASP.NET Core 代碼。

建立 ABP 應用

Nuget 安裝 Volo.Abp.AspNetCore.MvcVolo.Abp.Autofac

建立 AppModule 類,代碼以下:

[DependsOn(typeof(AbpAspNetCoreMvcModule))]
    [DependsOn(typeof(AbpAutofacModule))]
    public class AppModule : AbpModule
    {
        public override void OnApplicationInitialization(
            ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();
            app.UseConfiguredEndpoints();
        }
    }

在 Program 的 Host 加上 .UseServiceProviderFactory(new AutofacServiceProviderFactory()),示例以下:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())
            ...
            ...

而後在 Startup 中的 ConfiguraServices 方法中,添加 ABP 模塊, 而且設置使用 Autofac。

public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplication<AppModule>(options=>
            {
                options.UseAutofac();
            });
        }

定義權限

ABP 中使用 PermissionDefinitionProvider 類來定義權限,建立一個類,其代碼以下:

public class BookPermissionDefinitionProvider : PermissionDefinitionProvider
    {
        public override void Define(IPermissionDefinitionContext context)
        {
            var myGroup = context.AddGroup("Book");
            var permission = myGroup.AddPermission("Book");
            permission.AddChild("Book:Add");
            permission.AddChild("Book:Remove");
            permission.AddChild("Book:Select");
            permission.AddChild("Book:Update");
        }
    }

這裏定義了一個組 Book,定義了一個權限 Book了,Book 其下有四個子權限。

刪除 Startup 中的services.AddAuthorization(options =>...

將剩餘的依賴注入服務代碼移動到 AppModule 的 ConfigureServices 中。

Startup 的 Configure 改爲:

app.InitializeApplication();

AbpModule 中的 Configure 改爲:

var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseConfiguredEndpoints();

PermissionHandler 須要改爲:

public class PermissionHandler : IAuthorizationHandler
    {
        public Task HandleAsync(AuthorizationHandlerContext context)
        {
            // 當前訪問 Controller/Action 所須要的權限(策略受權)
            IAuthorizationRequirement[] pendingRequirements = context.PendingRequirements.ToArray();

            // 逐個檢查
            foreach (IAuthorizationRequirement requirement in pendingRequirements)
            {
                context.Succeed(requirement);
            }


            return Task.CompletedTask;
        }
    }

刪除 UserData 文件;BookController 須要修改一下登陸和憑證。

具體細節可參考倉庫源碼。

相關文章
相關標籤/搜索