ASP.NET Core 6框架揭秘實例演示[40]:基于角色的授權(quán)
ASP.NET應(yīng)用并沒有對如何定義授權(quán)策略做硬性規(guī)定,所以我們完全根據(jù)用戶具有的任意特性(如性別、年齡、學(xué)歷、所在地區(qū)、宗教信仰、政治面貌等)來判斷其是否具有獲取目標(biāo)資源或者執(zhí)行目標(biāo)操作的權(quán)限,但是針對角色的授權(quán)策略依然是最常用的。角色(或者用戶組)實際上就是對一組權(quán)限集的描述,將一個用戶添加到某個角色之中就是為了將對應(yīng)的權(quán)限賦予該用戶。在《使用最簡潔的代碼實現(xiàn)登錄、認證和注銷》中,我們提供了一個用來演示登錄、認證和注銷的程序,現(xiàn)在我們在此基礎(chǔ)上添加基于“角色授權(quán)的部分”。(本文提供的示例演示已經(jīng)同步到《ASP.NET Core 6框架揭秘-實例演示版》)
(資料圖)
[S2801]基于“要求”的授權(quán)[S2801]基于“要求”的授權(quán)[S2802]基于“策略”的授權(quán)[S2803]將“角色”綁定到路由終結(jié)點[S2804]將“授權(quán)策略”綁定到路由終結(jié)點
我們提供的演示實例提供了IAccountService和IPageRenderer兩個服務(wù),前者用用來進行校驗密鑰,后者用來呈現(xiàn)主頁和登錄頁面。為了在認證的時候一并將用戶擁有的角色提取出來,我們按照如下的方式為IAccountService接口的Validate方法添加了表示角色列表的輸出參數(shù)。對于實現(xiàn)類AccountService提供的三個賬號來說,只有“Bar”擁有一個名為“Admin”的角色。
public interface IAccountService{ bool Validate(string userName, string password, out string[] roles);}public class AccountService : IAccountService{ private readonly Dictionary _accounts = new(StringComparer.OrdinalIgnoreCase) { { "Foo", "password" }, { "Bar", "password" }, { "Baz", "password" } }; private readonly Dictionary _roles = new(StringComparer.OrdinalIgnoreCase) { { "Bar", new string[]{"Admin" } } }; public bool Validate(string userName, string password, out string[] roles) { if (_accounts.TryGetValue(userName, out var pwd) && pwd == password) { roles = _roles.TryGetValue(userName, out var value) ? value : Array.Empty(); return true; } roles = Array.Empty(); return false; }} 我們假設(shè)演示的應(yīng)用是供擁有“Admin”角色的管理人員使用的,所以只能擁有該角色的用戶才能訪問應(yīng)用的主頁,未授權(quán)訪問會自動定向到我們提供的“訪問拒絕”頁面。我們在另一個IPageRenderer服務(wù)接口中添加了如下這個RenderAccessDeniedPage方法,并在PageRenderer類型中完成了對應(yīng)的實現(xiàn)。
public interface IPageRenderer{ IResult RenderLoginPage(string? userName = null, string? password = null, string? errorMessage = null); IResult RenderAccessDeniedPage(string userName); IResult RenderHomePage(string userName);}public class PageRenderer : IPageRenderer{ public IResult RenderAccessDeniedPage(string userName) { var html = @$" Index {userName}, your access is denied.
Change another account "; return Results.Content(html, "text/html"); } ...}在現(xiàn)有的演示程序基礎(chǔ)上,我們不需要作太大的修改。由于需要引用授權(quán)功能,我們調(diào)用了IServiceCollection接口的AddAuthorization擴展方法注冊了必要的服務(wù)。由于引入了“訪問決絕”頁面,我們注冊了對應(yīng)的終結(jié)點,該終結(jié)點依然采用標(biāo)準(zhǔn)的路徑“Account/AccessDenied”,對應(yīng)的處理方法DenyAccess直接調(diào)用上面這個RenderAccessDeniedPage方法將該頁面呈現(xiàn)出來。
using App;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Authentication.Cookies;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Authorization.Infrastructure;using System.Security.Claims;using System.Security.Principal;var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton我們需要對用來認證請求的SignInAsync方法作相應(yīng)的修改。如下的代碼片段所示,對于成功通過認證的用戶,我們會為它創(chuàng)建一個ClaimsPrincipal對象來表示當(dāng)前用戶。這個對象也是授權(quán)的目標(biāo)對象,授權(quán)的本質(zhì)就是確定該對象是否攜帶了授權(quán)資源或者操作所要求的“資質(zhì)”。由于我們采用的是基于“角色”的授權(quán),所以我們將該用于擁有的角色以“聲明(Claim)”的形式添加到表示身份的ClaimsIdentity對象上。() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app.UseAuthentication();app.Map("/", WelcomeAsync);app.MapGet("Account/Login", Login);app.MapPost("Account/Login", SignInAsync);app.Map("Account/Logout", SignOutAsync);app.Map("Account/AccessDenied", DenyAccess);app.Run();Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer, IAuthorizationService authorizationService);IResult Login(IPageRenderer renderer);Task SignInAsync(HttpContext context, HttpRequest request, IPageRenderer renderer,IAccountService accountService);Task SignOutAsync(HttpContext context);IResult DenyAccess(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderAccessDeniedPage(user?.Identity?.Name!);
Task SignInAsync(HttpContext context, HttpRequest request, IPageRenderer renderer,IAccountService accountService){ var username = request.Form["username"]; if (string.IsNullOrEmpty(username)) { return renderer.RenderLoginPage(null, null, "Please enter user name.").ExecuteAsync(context); } var password = request.Form["password"]; if (string.IsNullOrEmpty(password)) { return renderer.RenderLoginPage(username, null, "Please enter user password.").ExecuteAsync(context); } if (!accountService.Validate(username, password, out var roles)) { return renderer.RenderLoginPage(username, null, "Invalid user name or password.").ExecuteAsync(context); } var identity = new GenericIdentity(name: username, type: CookieAuthenticationDefaults.AuthenticationScheme); foreach (var role in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); }var user = new ClaimsPrincipal(identity); return context.SignInAsync(user);}演示實例授權(quán)的效果就是讓擁有“Admin”角色的用戶才能訪問主頁,所以我們將授權(quán)實現(xiàn)在如下這個WelcomeAsync方法中。如果當(dāng)前用戶(由注入的ClaimsPrincipal對象表示)并未通過認證,我們依然調(diào)用HttpContext上下文的ChallengeAsync擴展方法返回一個“匿名請求”的質(zhì)詢。在確定用戶通過認證的前提下,我們創(chuàng)建了一個RolesAuthorizationRequirement來表示主頁針對授權(quán)用戶的“角色要求”。授權(quán)檢驗通過調(diào)用注入的IAuthorizationService對象的AuthorizeAsync方法來完成,我們將代表當(dāng)前用戶的ClaimsPrincipal對象和包含RolesAuthorizationRequirement對象的數(shù)組作為參數(shù)。如果授權(quán)成功,主頁得以正常呈現(xiàn),否則我們調(diào)用HttpContext上下文的ForbidAsync擴展方法返回“權(quán)限不足”的質(zhì)詢,上面提供的“拒絕訪問”頁面將會呈現(xiàn)出來。
async Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer,IAuthorizationService authorizationService){ if (user?.Identity?.IsAuthenticated ?? false) { var requirement = new RolesAuthorizationRequirement(new string[] { "admin" }); var result = await authorizationService.AuthorizeAsync( user:user, resource: null, requirements: new IAuthorizationRequirement[] { requirement }); if (result.Succeeded) { await renderer.RenderHomePage(user.Identity.Name!).ExecuteAsync(context); } else { await context.ForbidAsync(); } } else { await context.ChallengeAsync(); }}程序啟動之后,具有“Admin”權(quán)限的“Bar”用戶能夠正常主頁,其他的用戶(比如“Foo”)會自動重定向到“訪問拒絕”頁面,具體效果體現(xiàn)在圖1中。
圖1 針對主頁的授權(quán)
[S2802]基于“策略”的授權(quán)我們調(diào)用IAuthorizationService服務(wù)的AuthorizeAsync方法進行授權(quán)檢驗的時候,實際上是將授權(quán)要求定義在一個RolesAuthorizationRequirement對象中,這是一種比較煩瑣的編程方式。另一種推薦的做法是在應(yīng)用啟動的過程中創(chuàng)建一系列通過AuthorizationPolicy對象表示的授權(quán)規(guī)則,并指定一個唯一的名稱對它們進行全局注冊,那么后續(xù)就可以針對注冊的策略名稱進行授權(quán)檢驗。如下面的代碼片段所示,在調(diào)用AddAuthorization擴展方法注冊授權(quán)相關(guān)服務(wù)時,我們利用作為輸入?yún)?shù)的Action
using App;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Authentication.Cookies;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Authorization.Infrastructure;using System.Security.Claims;using System.Security.Principal;var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton在呈現(xiàn)主頁的WelcomeAsync方法中,我們依然調(diào)用IAuthorizationService服務(wù)的AuthorizeAsync方法來檢驗用戶是否具有對應(yīng)的權(quán)限,但這次采用的是另一個可以直接指定授權(quán)策略注冊名稱的AuthorizeAsync方法重載(S2802)。() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization(AddAuthorizationPolicy);var app = builder.Build();app.UseAuthentication();app.Map("/", WelcomeAsync);app.MapGet("Account/Login", Login);app.MapPost("Account/Login", SignInAsync);app.Map("Account/Logout", SignOutAsync);app.Map("Account/AccessDenied", DenyAccess);app.Run();void AddAuthorizationPolicy(AuthorizationOptions options){ var requirement = new RolesAuthorizationRequirement(new string[] { "admin" }); var requirements = new IAuthorizationRequirement[] { requirement }; var policy = new AuthorizationPolicy(requirements: requirements, authenticationSchemes: Array.Empty ()); options.AddPolicy("Home", policy);}
async Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer, IAuthorizationService authorizationService){ if (user?.Identity?.IsAuthenticated ?? false) { var result = await authorizationService.AuthorizeAsync(user: user, policyName: "Home");if (result.Succeeded) { await renderer.RenderHomePage(user.Identity.Name!).ExecuteAsync(context); } else { await context.ForbidAsync(); } } else { await context.ChallengeAsync(); }}[S2803]將“角色”綁定到路由終結(jié)點上面演示的例子都調(diào)用IAuthorizationService對象的AuthorizeAsync方法來確定指定的用戶是否滿足提供的授權(quán)規(guī)則,實際上針對請求的授權(quán)直接交給AuthorizationMiddleware中間件來完成,該中間件可以采用如下的方式調(diào)用UseAuthorization擴展方法進行注冊。
...var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app .UseAuthentication() .UseAuthorization();...
當(dāng)該中間件在進行授權(quán)檢驗的時候,會從當(dāng)前終結(jié)點的元數(shù)據(jù)中提取授權(quán)規(guī)則,所以我們在注冊對應(yīng)終結(jié)點的時候需要提供對應(yīng)的授權(quán)規(guī)則。由于WelcomeAsync方法不再需要自行完成授權(quán)檢驗,所以它只需要將主頁呈現(xiàn)出來就可以了。針對“Admin”角色的授權(quán)要求直接利用標(biāo)注在該方法上的AuthorizeAttribute特性來指定,該特性就是為AuthorizationMiddleware中間件提供授權(quán)規(guī)則的元數(shù)據(jù)(S2803)。
[Authorize(Roles ="admin")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer)=> renderer.RenderHomePage(user.Identity!.Name!);[S2804]將“授權(quán)策略”綁定到路由終結(jié)點
如果在調(diào)用AddAuthorization擴展方法時已經(jīng)定義了授權(quán)策略,我們也可以按照如下的方式將策略名稱設(shè)置為AuthorizeAttribute特性大的Policy屬性(S2804)。
[Authorize(Policy = "Home")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!);
如果采用Lambda表達式來定義終結(jié)點處理器,我們可以按照如下的方式將AuthorizeAttribute特性標(biāo)注在表達式上。注冊終結(jié)點的各種Map方法會返回一個IEndpointConventionBuilder對象,我們可以安裝如下的方式調(diào)用它的RequireAuthorization擴展方法將AuthorizeAttribute特性作為一個IAuthorizeData對象添加到注冊終結(jié)點的元數(shù)據(jù)集合。RequireAuthorization擴展方法來有一個將授權(quán)策略名稱作為參數(shù)的重載。
app.Map("/",[Authorize(Roles ="admin")]ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!));app.Map("/",[Authorize(Policy = "Home")](ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!));app.Map("/", WelcomeAsync).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin"});app.Map("/", WelcomeAsync).RequireAuthorization(new AuthorizeAttribute { Policy = "Home"});app.Map("/", WelcomeAsync).RequireAuthorization(policyNames: "Home"); 關(guān)鍵詞:
[責(zé)任編輯:xwzkw]
相關(guān)閱讀
- (2023-06-25)ASP.NET Core 6框架揭秘實例演示[40]:基于角色的授權(quán)
- (2023-06-25)自貢治銀屑病哪個醫(yī)院好 全球快消息
- (2023-06-25)焦點播報:找到端午節(jié)新的“打開方式”
- (2023-06-25)北京低效樓宇加速“改頭換面”
- (2023-06-25)世界要聞:曾給普京上菜的“大廚”,如今成了“大麻煩”
- (2023-06-25)世界新動態(tài):武鐵端午假期客流追平2019年同期
- (2023-06-25)“Meet the World Around”在深留學(xué)生菁英交流營開放報名
- (2023-06-25)每日資訊:戚繼光艦圓滿完成遠海遠域?qū)嵙?xí)訪問任務(wù)凱旋
- (2023-06-25)千筆樓 | 這個端午,我們看到了什么?_環(huán)球觀焦點
- (2023-06-25)環(huán)球快看點丨兒童驚厥家長求助 靜安交警護送贏得時間
- (2023-06-25)“好吃”又好玩!暑期臨近,精選9條避暑鐵路線路來了! 每日消息
- (2023-06-25)天天即時:ANRPC:割膠活動恢復(fù) 5月全球天然橡膠產(chǎn)量增加
- (2023-06-25)貴州省近年來破獲的百人以上團伙案件和典型新型毒品案件對外公布
- (2023-06-25)【天天新視野】降息了,長沙已有購房者享受首套房貸利率4%
- (2023-06-25)代運營一個月多少錢?代運營收費項目和收費標(biāo)準(zhǔn)-天天速遞
- (2023-06-25)回看小長假|(zhì)吉林機場集團運送旅客14.2萬人次|天天熱訊
- (2023-06-25)7月1日起實行新的列車運行圖
- (2023-06-25)在蘇高校陸續(xù)公布招生方案:省內(nèi)招生計劃增加,新興專業(yè)應(yīng)運而生
- (2023-06-25)天天觀速訊丨端午假期清遠旅游數(shù)據(jù)出爐 多家民宿訂房率九成以上
- (2023-06-25)喝牛奶可以養(yǎng)胃嗎?
- (2023-06-25)【世界新視野】來賓市興賓區(qū):小南瓜 迎豐收
- (2023-06-25)天天實時:北京教育考試院公布北京高招錄取主要日程安排
- (2023-06-25)文明教育引導(dǎo)“十大文明行動” |交通安全很重要 文明出行記心間-世界播資訊
- (2023-06-25)速約!廈門宮頸癌疫苗接種專場來了!
- (2023-06-25)46歲曾黎和48歲梅婷同臺, “打針臉”和天然臉的差距一目了然
- (2023-06-25)貴安:“稅收金課堂”為納稅人充電續(xù)航_當(dāng)前資訊
- (2023-06-25)烏海市總工會機關(guān)黨支部開展主題黨日暨“我們的節(jié)日·端午”活動_焦點報道
- (2023-06-25)貴州旅游地標(biāo) ⑤ | 世界自然遺產(chǎn)梵凈山
- (2023-06-25)高價收藏實為陷阱構(gòu)成詐騙獲刑罰金 焦點速看
- (2023-06-25)四川彭山開展“我們的中國夢——文化進萬家”活動





