ASP.NET?Core中的策略授權(quán)和ABP授權(quán)
ASP.NET Core 中的策略授權(quán)
首先我們來創(chuàng)建一個 WebAPI 應(yīng)用。
然后引入 Microsoft.AspNetCore.Authentication.JwtBearer
包。
策略
Startup 類的 ConfigureServices 方法中,添加一個策略的形式如下:
services.AddAuthorization(options => { options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); });
這里我們分步來說。
services.AddAuthorization 用于添加授權(quán)方式,目前只支持 AddPolicy。
ASP.NET Core 中,有基于角色、聲明、策略的三種授權(quán)形式,都是使用 AddPolicy
來添加授權(quán)處理。
其中,有兩個 API 如下:
public void AddPolicy(string name, AuthorizationPolicy policy); public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy);
name = "AtLeast21"
,這里 "AtLeast21" 是策略的名稱。
policy.Requirements.Add()
用于添加一個策略的標(biāo)記(存儲此策略的數(shù)據(jù)),此標(biāo)記需要繼承 IAuthorizationRequirement
接口。
策略的名稱應(yīng)該如何設(shè)置呢?在授權(quán)上應(yīng)該如何編寫策略以及使用 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"; } }
功能很簡單,就是對列表內(nèi)容增刪查改。
設(shè)定權(quán)限
前面我們創(chuàng)建了 BookController
,具有增刪查改的功能。應(yīng)該為每一個功能都應(yīng)該設(shè)置一種權(quán)限。
ASP.NET Core 中,一個權(quán)限標(biāo)記,需要繼承IAuthorizationRequirement
接口。
我們來設(shè)置五個權(quán)限:
添加一個文件,填寫以下代碼。
/* IAuthorizationRequirement 是一個空接口,具體對于授權(quán)的需求,其屬性等信息是自定義的 這里的繼承關(guān)系也沒有任何意義 */ // 能夠訪問 Book 的權(quán)限 public class BookRequirment : IAuthorizationRequirement { } // 增刪查改 Book 權(quán)限 // 可以繼承 IAuthorizationRequirement ,也可以繼承 BookRequirment public class BookAddRequirment : BookRequirment { } public class BookRemoveRequirment : BookRequirment { } public class BookSelectRequirment : BookRequirment { } public class BookUpdateRequirment : BookRequirment { }
BookRequirment 代表能夠訪問 BookController,其它四個分別代表增刪查改的權(quán)限。
定義策略
權(quán)限設(shè)定后,我們開始設(shè)置策略。
在 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()); }); });
這里我們?yōu)槊糠N策略只設(shè)置一種權(quán)限,當(dāng)然每種策略都可以添加多個權(quán)限,
這里名稱使用 :
隔開,主要是為了可讀性,讓人一看就知道是層次關(guān)系。
存儲用戶信息
這里為了更加簡單,就不使用數(shù)據(jù)庫了。
以下用戶信息結(jié)構(gòu)是隨便寫的。用戶-角色-角色具有的權(quán)限。
這個權(quán)限用什么類型存儲都可以。只要能夠標(biāo)識區(qū)分是哪個權(quán)限就行。
/// <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) } } }); // 沒有刪除權(quán)限 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> /// 這里的存儲角色的策略授權(quán),字符串?dāng)?shù)字等都行,只要能夠存儲表示就OK /// <para>在這里沒有任何意義,只是標(biāo)識的一種方式</param> /// </summary> public class Role { public List<Type> Requirements { get; set; } }
標(biāo)記訪問權(quán)限
定義策略完畢后,就要為 Controller 和 Action 標(biāo)記訪問權(quán)限了。
使用 [Authorize(Policy = "{string}")]
特性和屬性來設(shè)置訪問此 Controller 、 Action 所需要的權(quán)限。
這里我們分開設(shè)置,每個功能標(biāo)記一種權(quán)限(最小粒度應(yīng)該是一個功能 ,而不是一個 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){} }
認(rèn)證:Token 憑據(jù)
因?yàn)槭褂玫氖?WebAPI,所以使用 Bearer Token 認(rèn)證,當(dāng)然使用 Cookie 等也可以。使用什么認(rèn)證方式都可以。
// 設(shè)置驗(yàn)證方式為 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的密鑰 // 是否驗(yàn)證發(fā)布者 ValidateIssuer = true, // 發(fā)布者名稱 ValidIssuer = "server", // 是否驗(yàn)證訂閱者 // 訂閱者名稱 ValidateAudience = true, ValidAudience = "client007", // 是否驗(yàn)證令牌有效期 ValidateLifetime = true, // 每次頒發(fā)令牌,令牌有效時間 ClockSkew = TimeSpan.FromMinutes(120) }; });
上面的代碼是一個模板,可以隨便改。這里的認(rèn)證方式跟我們的策略授權(quán)沒什么關(guān)系。
頒發(fā)登錄憑據(jù)
下面這個 Action 放置到 BookController,作為登錄功能。這一部分也不重要,主要是為用戶頒發(fā)憑據(jù),以及標(biāo)識用戶。用戶的 Claim 可以存儲此用戶的唯一標(biāo)識。
/// <summary> /// 用戶登錄并且頒發(fā)憑據(jù) /// </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 中補(bǔ)充以下兩行:
app.UseAuthentication(); app.UseAuthorization();
自定義授權(quán)
自定義授權(quán)需要繼承 IAuthorizationHandler
接口,實(shí)現(xiàn)此接口的類能夠決定是否對用戶的訪問進(jìn)行授權(quán)。
實(shí)現(xiàn)代碼如下:
/// <summary> /// 判斷用戶是否具有權(quán)限 /// </summary> public class PermissionHandler : IAuthorizationHandler { public async Task HandleAsync(AuthorizationHandlerContext context) { // 當(dāng)前訪問 Controller/Action 所需要的權(quán)限(策略授權(quán)) 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; } // ... 省略一些檢驗(yàn)過程 ... // 獲取此用戶的信息 User user = UsersData.Users.FirstOrDefault(x => x.Name.Equals(userName.Value, StringComparison.OrdinalIgnoreCase)); List<Type> auths = user.Role.Requirements; // 逐個檢查 foreach (IAuthorizationRequirement requirement in pendingRequirements) { // 如果用戶權(quán)限列表中沒有找到此權(quán)限的話 if (!auths.Any(x => x == requirement.GetType())) context.Fail(); context.Succeed(requirement); } await Task.CompletedTask; } }
過程:
- 從上下文(Context) 中獲取用戶信息(context.User)
- 獲取此用戶所屬的角色,并獲取此角色具有的權(quán)限
- 獲取此次請求的 Controller/Action 需要的權(quán)限(context.PendingRequirements)
- 檢查所需要的權(quán)限(foreach循環(huán)),此用戶是否都具有
最后需要將此接口、服務(wù),注冊到容器中:
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
做完這些后,就可以測試授權(quán)了。
IAuthorizationService
前面實(shí)現(xiàn)了 IAuthorizationHandler 接口的類,用于自定義確定用戶是否有權(quán)訪問此 Controller/Action。
IAuthorizationService 接口用于確定授權(quán)是否成功,其定義如下:
public interface IAuthorizationService { Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement> requirements); Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName); }
DefaultAuthorizationService
接口實(shí)現(xiàn)了 IAuthorizationService
,ASP.NET Core 默認(rèn)使用 DefaultAuthorizationService
來確認(rèn)授權(quán)。
前面我們使用 IAuthorizationHandler
接口來自定義授權(quán),如果再深入一層的話,就追溯到了IAuthorizationService
。
DefaultAuthorizationService
是 IAuthorizationService
的默認(rèn)實(shí)現(xiàn),其中有一段代碼如下:
DefaultAuthorizationService
比較復(fù)雜,一般情況下,我們只要實(shí)現(xiàn) IAuthorizationHandler
就夠了。
ABP 授權(quán)
前面已經(jīng)介紹了 ASP.NET Core 中的策略授權(quán),這里介紹一下 ABP 中的授權(quán),我們繼續(xù)利用前面已經(jīng)實(shí)現(xiàn)的 ASP.NET Core 代碼。
創(chuàng)建 ABP 應(yīng)用
Nuget 安裝 Volo.Abp.AspNetCore.Mvc
、Volo.Abp.Autofac
。
創(chuàng)建 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 模塊, 并且設(shè)置使用 Autofac。
public void ConfigureServices(IServiceCollection services) { services.AddApplication<AppModule>(options=> { options.UseAutofac(); }); }
定義權(quán)限
ABP 中使用 PermissionDefinitionProvider
類來定義權(quán)限,創(chuàng)建一個類,其代碼如下:
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
,定義了一個權(quán)限 Book
了,Book
其下有四個子權(quán)限。
刪除 Startup 中的services.AddAuthorization(options =>...
。
將剩余的依賴注入服務(wù)代碼移動到 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) { // 當(dāng)前訪問 Controller/Action 所需要的權(quán)限(策略授權(quán)) IAuthorizationRequirement[] pendingRequirements = context.PendingRequirements.ToArray(); // 逐個檢查 foreach (IAuthorizationRequirement requirement in pendingRequirements) { context.Succeed(requirement); } return Task.CompletedTask; } }
刪除 UserData 文件;BookController 需要修改一下登錄和憑證。
到此這篇關(guān)于ASP.NET Core策略授權(quán)和ABP授權(quán)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.NET Windbg分析某婦產(chǎn)醫(yī)院WPF內(nèi)存溢出
這篇文章主要為大家介紹了.NET Windbg分析某婦產(chǎn)醫(yī)院WPF內(nèi)存溢出,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06ASP.NET?Core?6.0對熱重載的支持實(shí)例詳解
.NET 熱重載會將代碼更改(包括對樣式表的更改)應(yīng)用到正在運(yùn)行的應(yīng)用,而無需重啟應(yīng)用,也不會丟失應(yīng)用狀態(tài),下面這篇文章主要給大家介紹了關(guān)于ASP.NET?Core?6.0對熱重載支持的相關(guān)資料,需要的朋友可以參考下2022-03-03linq to sql 中,如何解決多條件查詢問題,答案,用表達(dá)式樹! (下)
在上一篇中,我們做了基于linq to sql 的多條件組合查詢,但通過監(jiān)視數(shù)據(jù)庫發(fā)現(xiàn),這樣做的成本比較高,每次都要取出全部的數(shù)據(jù)到內(nèi)存進(jìn)行篩選.2011-08-08Visual Studio 2017 IDE安裝使用圖文教程
這篇文章主要為大家詳細(xì)介紹了Visual Studio 2017 IDE安裝使用圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09asp.net 字符串、二進(jìn)制、編碼數(shù)組轉(zhuǎn)換函數(shù)
字符串和二進(jìn)制數(shù)組轉(zhuǎn)換、將HTML文件顯示為頁面的一部分、UTF8和GB2312之間的轉(zhuǎn)換2010-01-01ASP.NET Core中如何使用表達(dá)式樹創(chuàng)建URL詳解
這篇文章主要給大家介紹了關(guān)于ASP.NET Core中如何使用表達(dá)式樹創(chuàng)建URL的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10