ASP.NET Core 6框架揭秘實例演示[40]:基于角色的授權
ASP.NET應用并沒有對如何定義授權策略做硬性規定,所以我們完全根據用戶具有的任意特性(如性別、年齡、學歷、所在地區、宗教信仰、政治面貌等)來判斷其是否具有獲取目標資源或者執行目標操作的權限,但是針對角色的授權策略依然是最常用的。角色(或者用戶組)實際上就是對一組權限集的描述,將一個用戶添加到某個角色之中就是為了將對應的權限賦予該用戶。在《使用最簡潔的代碼實現登錄、認證和注銷》中,我們提供了一個用來演示登錄、認證和注銷的程序,現在我們在此基礎上添加基于“角色授權的部分”。(本文提供的示例演示已經同步到《ASP.NET Core 6框架揭秘-實例演示版》)
(資料圖)
[S2801]基于“要求”的授權[S2801]基于“要求”的授權[S2802]基于“策略”的授權[S2803]將“角色”綁定到路由終結點[S2804]將“授權策略”綁定到路由終結點
我們提供的演示實例提供了IAccountService和IPageRenderer兩個服務,前者用用來進行校驗密鑰,后者用來呈現主頁和登錄頁面。為了在認證的時候一并將用戶擁有的角色提取出來,我們按照如下的方式為IAccountService接口的Validate方法添加了表示角色列表的輸出參數。對于實現類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; }} 我們假設演示的應用是供擁有“Admin”角色的管理人員使用的,所以只能擁有該角色的用戶才能訪問應用的主頁,未授權訪問會自動定向到我們提供的“訪問拒絕”頁面。我們在另一個IPageRenderer服務接口中添加了如下這個RenderAccessDeniedPage方法,并在PageRenderer類型中完成了對應的實現。
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"); } ...}在現有的演示程序基礎上,我們不需要作太大的修改。由于需要引用授權功能,我們調用了IServiceCollection接口的AddAuthorization擴展方法注冊了必要的服務。由于引入了“訪問決絕”頁面,我們注冊了對應的終結點,該終結點依然采用標準的路徑“Account/AccessDenied”,對應的處理方法DenyAccess直接調用上面這個RenderAccessDeniedPage方法將該頁面呈現出來。
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方法作相應的修改。如下的代碼片段所示,對于成功通過認證的用戶,我們會為它創建一個ClaimsPrincipal對象來表示當前用戶。這個對象也是授權的目標對象,授權的本質就是確定該對象是否攜帶了授權資源或者操作所要求的“資質”。由于我們采用的是基于“角色”的授權,所以我們將該用于擁有的角色以“聲明(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);}演示實例授權的效果就是讓擁有“Admin”角色的用戶才能訪問主頁,所以我們將授權實現在如下這個WelcomeAsync方法中。如果當前用戶(由注入的ClaimsPrincipal對象表示)并未通過認證,我們依然調用HttpContext上下文的ChallengeAsync擴展方法返回一個“匿名請求”的質詢。在確定用戶通過認證的前提下,我們創建了一個RolesAuthorizationRequirement來表示主頁針對授權用戶的“角色要求”。授權檢驗通過調用注入的IAuthorizationService對象的AuthorizeAsync方法來完成,我們將代表當前用戶的ClaimsPrincipal對象和包含RolesAuthorizationRequirement對象的數組作為參數。如果授權成功,主頁得以正常呈現,否則我們調用HttpContext上下文的ForbidAsync擴展方法返回“權限不足”的質詢,上面提供的“拒絕訪問”頁面將會呈現出來。
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”權限的“Bar”用戶能夠正常主頁,其他的用戶(比如“Foo”)會自動重定向到“訪問拒絕”頁面,具體效果體現在圖1中。
圖1 針對主頁的授權
[S2802]基于“策略”的授權我們調用IAuthorizationService服務的AuthorizeAsync方法進行授權檢驗的時候,實際上是將授權要求定義在一個RolesAuthorizationRequirement對象中,這是一種比較煩瑣的編程方式。另一種推薦的做法是在應用啟動的過程中創建一系列通過AuthorizationPolicy對象表示的授權規則,并指定一個唯一的名稱對它們進行全局注冊,那么后續就可以針對注冊的策略名稱進行授權檢驗。如下面的代碼片段所示,在調用AddAuthorization擴展方法注冊授權相關服務時,我們利用作為輸入參數的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在呈現主頁的WelcomeAsync方法中,我們依然調用IAuthorizationService服務的AuthorizeAsync方法來檢驗用戶是否具有對應的權限,但這次采用的是另一個可以直接指定授權策略注冊名稱的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]將“角色”綁定到路由終結點上面演示的例子都調用IAuthorizationService對象的AuthorizeAsync方法來確定指定的用戶是否滿足提供的授權規則,實際上針對請求的授權直接交給AuthorizationMiddleware中間件來完成,該中間件可以采用如下的方式調用UseAuthorization擴展方法進行注冊。
...var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app .UseAuthentication() .UseAuthorization();...
當該中間件在進行授權檢驗的時候,會從當前終結點的元數據中提取授權規則,所以我們在注冊對應終結點的時候需要提供對應的授權規則。由于WelcomeAsync方法不再需要自行完成授權檢驗,所以它只需要將主頁呈現出來就可以了。針對“Admin”角色的授權要求直接利用標注在該方法上的AuthorizeAttribute特性來指定,該特性就是為AuthorizationMiddleware中間件提供授權規則的元數據(S2803)。
[Authorize(Roles ="admin")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer)=> renderer.RenderHomePage(user.Identity!.Name!);[S2804]將“授權策略”綁定到路由終結點
如果在調用AddAuthorization擴展方法時已經定義了授權策略,我們也可以按照如下的方式將策略名稱設置為AuthorizeAttribute特性大的Policy屬性(S2804)。
[Authorize(Policy = "Home")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!);
如果采用Lambda表達式來定義終結點處理器,我們可以按照如下的方式將AuthorizeAttribute特性標注在表達式上。注冊終結點的各種Map方法會返回一個IEndpointConventionBuilder對象,我們可以安裝如下的方式調用它的RequireAuthorization擴展方法將AuthorizeAttribute特性作為一個IAuthorizeData對象添加到注冊終結點的元數據集合。RequireAuthorization擴展方法來有一個將授權策略名稱作為參數的重載。
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"); 關鍵詞:
[責任編輯:xwzkw]
相關閱讀
- (2023-06-25)ASP.NET Core 6框架揭秘實例演示[40]:基于角色的授權
- (2023-06-25)自貢治銀屑病哪個醫院好 全球快消息
- (2023-06-25)焦點播報:找到端午節新的“打開方式”
- (2023-06-25)北京低效樓宇加速“改頭換面”
- (2023-06-25)世界要聞:曾給普京上菜的“大廚”,如今成了“大麻煩”
- (2023-06-25)世界新動態:武鐵端午假期客流追平2019年同期
- (2023-06-25)“Meet the World Around”在深留學生菁英交流營開放報名
- (2023-06-25)每日資訊:戚繼光艦圓滿完成遠海遠域實習訪問任務凱旋
- (2023-06-25)千筆樓 | 這個端午,我們看到了什么?_環球觀焦點
- (2023-06-25)環球快看點丨兒童驚厥家長求助 靜安交警護送贏得時間
- (2023-06-25)“好吃”又好玩!暑期臨近,精選9條避暑鐵路線路來了! 每日消息
- (2023-06-25)天天即時:ANRPC:割膠活動恢復 5月全球天然橡膠產量增加
- (2023-06-25)貴州省近年來破獲的百人以上團伙案件和典型新型毒品案件對外公布
- (2023-06-25)【天天新視野】降息了,長沙已有購房者享受首套房貸利率4%
- (2023-06-25)代運營一個月多少錢?代運營收費項目和收費標準-天天速遞
- (2023-06-25)回看小長假|吉林機場集團運送旅客14.2萬人次|天天熱訊
- (2023-06-25)7月1日起實行新的列車運行圖
- (2023-06-25)在蘇高校陸續公布招生方案:省內招生計劃增加,新興專業應運而生
- (2023-06-25)天天觀速訊丨端午假期清遠旅游數據出爐 多家民宿訂房率九成以上
- (2023-06-25)喝牛奶可以養胃嗎?
- (2023-06-25)【世界新視野】來賓市興賓區:小南瓜 迎豐收
- (2023-06-25)天天實時:北京教育考試院公布北京高招錄取主要日程安排
- (2023-06-25)文明教育引導“十大文明行動” |交通安全很重要 文明出行記心間-世界播資訊
- (2023-06-25)速約!廈門宮頸癌疫苗接種專場來了!
- (2023-06-25)46歲曾黎和48歲梅婷同臺, “打針臉”和天然臉的差距一目了然
- (2023-06-25)貴安:“稅收金課堂”為納稅人充電續航_當前資訊
- (2023-06-25)烏海市總工會機關黨支部開展主題黨日暨“我們的節日·端午”活動_焦點報道
- (2023-06-25)貴州旅游地標 ⑤ | 世界自然遺產梵凈山
- (2023-06-25)高價收藏實為陷阱構成詐騙獲刑罰金 焦點速看
- (2023-06-25)四川彭山開展“我們的中國夢——文化進萬家”活動





