ASP.NET?Core中的策略授權(quán)和ABP授權(quán)
ASP.NET Core 中的策略授權(quán)
首先我們來創(chuàng)建一個(gè) WebAPI 應(yīng)用。
然后引入 Microsoft.AspNetCore.Authentication.JwtBearer 包。
策略
Startup 類的 ConfigureServices 方法中,添加一個(gè)策略的形式如下:
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)處理。
其中,有兩個(gè) API 如下:
public void AddPolicy(string name, AuthorizationPolicy policy);
public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy);name = "AtLeast21",這里 "AtLeast21" 是策略的名稱。
policy.Requirements.Add() 用于添加一個(gè)策略的標(biāo)記(存儲(chǔ)此策略的數(shù)據(jù)),此標(biāo)記需要繼承 IAuthorizationRequirement 接口。
策略的名稱應(yīng)該如何設(shè)置呢?在授權(quán)上應(yīng)該如何編寫策略以及使用 Requirements.Add()?
這里先放一放,我們接下來再講解。
定義一個(gè) Controller
我們來添加一個(gè) 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)該為每一個(gè)功能都應(yīng)該設(shè)置一種權(quán)限。
ASP.NET Core 中,一個(gè)權(quán)限標(biāo)記,需要繼承IAuthorizationRequirement 接口。
我們來設(shè)置五個(gè)權(quán)限:
添加一個(gè)文件,填寫以下代碼。
/*
IAuthorizationRequirement 是一個(gè)空接口,具體對于授權(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,其它四個(gè)分別代表增刪查改的權(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)然每種策略都可以添加多個(gè)權(quán)限,
這里名稱使用 : 隔開,主要是為了可讀性,讓人一看就知道是層次關(guān)系。
存儲(chǔ)用戶信息
這里為了更加簡單,就不使用數(shù)據(jù)庫了。
以下用戶信息結(jié)構(gòu)是隨便寫的。用戶-角色-角色具有的權(quán)限。
這個(gè)權(quán)限用什么類型存儲(chǔ)都可以。只要能夠標(biāo)識(shí)區(qū)分是哪個(gè)權(quán)限就行。
/// <summary>
/// 存儲(chǔ)用戶信息
/// </summary>
public static class UsersData
{
public static readonly List<User> Users = new List<User>();
static UsersData()
{
// 添加一個(gè)管理員
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>
/// 這里的存儲(chǔ)角色的策略授權(quán),字符串?dāng)?shù)字等都行,只要能夠存儲(chǔ)表示就OK
/// <para>在這里沒有任何意義,只是標(biāo)識(shí)的一種方式</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è)置,每個(gè)功能標(biāo)記一種權(quán)限(最小粒度應(yīng)該是一個(gè)功能 ,而不是一個(gè) 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ā)令牌,令牌有效時(shí)間
ClockSkew = TimeSpan.FromMinutes(120)
};
});上面的代碼是一個(gè)模板,可以隨便改。這里的認(rèn)證方式跟我們的策略授權(quán)沒什么關(guān)系。
頒發(fā)登錄憑據(jù)
下面這個(gè) Action 放置到 BookController,作為登錄功能。這一部分也不重要,主要是為用戶頒發(fā)憑據(jù),以及標(biāo)識(shí)用戶。用戶的 Claim 可以存儲(chǔ)此用戶的唯一標(biāo)識(shí)。
/// <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;
// 逐個(gè)檢查
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)建一個(gè)類,其代碼如下:
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");
}
}這里定義了一個(gè)組 Book,定義了一個(gè)權(quán)限 Book了,Book 其下有四個(gè)子權(quán)限。
刪除 Startup 中的services.AddAuthorization(options =>... 。
將剩余的依賴注入服務(wù)代碼移動(dòng)到 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();
// 逐個(gè)檢查
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)文章
詳解ASP.NET Core 之 Identity 入門(一)
本篇文章主要介紹了ASP.NET Core 之 Identity 入門,主要負(fù)責(zé)對用戶的身份進(jìn)行認(rèn)證,有興趣的可以了解一下。2016-12-12
ASP.NET MVC圖片上傳前預(yù)覽簡單實(shí)現(xiàn)
這篇文章主要介紹了ASP.NET MVC圖片上傳前預(yù)覽簡單實(shí)現(xiàn)代碼,可以獲取圖片文件名和圖片字節(jié)大小,感興趣的小伙伴們可以參考一下2016-05-05
ASP.NET編程簡單實(shí)現(xiàn)生成靜態(tài)頁面的方法【附demo源碼下載】
這篇文章主要介紹了ASP.NET編程簡單實(shí)現(xiàn)生成靜態(tài)頁面的方法,較為詳細(xì)的分析了asp.net生成靜態(tài)頁面的步驟與相關(guān)操作技巧,并附帶相關(guān)實(shí)例源碼供讀者下載參考,需要的朋友可以參考下2017-07-07
如此高效通用的分頁存儲(chǔ)過程是帶有sql注入漏洞的zt
通常大家都會(huì)認(rèn)為存儲(chǔ)過程可以避免sql注入的漏洞,這適用于一般的存儲(chǔ)過程,而對于通用分頁存儲(chǔ)過程是不適合的,請看下面的代碼和分析!2010-07-07
在VS2009中集成自動(dòng)上傳nuget包到服務(wù)器的解決方案
這篇文章主要介紹了在VS2009中集成自動(dòng)上傳nuget包到服務(wù)器的解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Asp.net下用JQuery找出哪一個(gè)元素引起PostBack
在Asp.net webform中,如何找出哪一個(gè)按鈕觸發(fā)Button PostBack事件。2010-06-06

