99久久人妻无码精品系列蜜桃|欧美一区二区三区乱码AⅤ|精品国语对白精品自拍视|在线视频免费观看一区|98av国产欧美日韩亚洲欧洲|人妻丰满熟妇av无码区二区三区|强乱中文字幕av一区乱码|亚洲日本一区二区

您的位置:首頁 > 滾動 >

ASP.NET Core 6框架揭秘實例演示[40]:基于角色的授權(quán)

2023-06-25 17:45:41 來源:博客園

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)登錄、認(rèn)證和注銷》中,我們提供了一個用來演示登錄、認(rèn)證和注銷的程序,現(xiàn)在我們在此基礎(chǔ)上添加基于“角色授權(quán)的部分”。(本文提供的示例演示已經(jīng)同步到《ASP.NET Core 6框架揭秘-實例演示版》)


(資料圖)

[S2801]基于“要求”的授權(quán)[S2802]基于“策略”的授權(quán)[S2803]將“角色”綁定到路由終結(jié)點[S2804]將“授權(quán)策略”綁定到路由終結(jié)點

[S2801]基于“要求”的授權(quán)

我們提供的演示實例提供了IAccountService和IPageRenderer兩個服務(wù),前者用用來進(jìn)行校驗密鑰,后者用來呈現(xiàn)主頁和登錄頁面。為了在認(rè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擴(kuò)展方法注冊了必要的服務(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()    .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!);
我們需要對用來認(rèn)證請求的SignInAsync方法作相應(yīng)的修改。如下的代碼片段所示,對于成功通過認(rèn)證的用戶,我們會為它創(chuàng)建一個ClaimsPrincipal對象來表示當(dāng)前用戶。這個對象也是授權(quán)的目標(biāo)對象,授權(quán)的本質(zhì)就是確定該對象是否攜帶了授權(quán)資源或者操作所要求的“資質(zhì)”。由于我們采用的是基于“角色”的授權(quán),所以我們將該用于擁有的角色以“聲明(Claim)”的形式添加到表示身份的ClaimsIdentity對象上。
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對象表示)并未通過認(rèn)證,我們依然調(diào)用HttpContext上下文的ChallengeAsync擴(kuò)展方法返回一個“匿名請求”的質(zhì)詢。在確定用戶通過認(rèn)證的前提下,我們創(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擴(kuò)展方法返回“權(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方法進(jìn)行授權(quán)檢驗的時候,實際上是將授權(quán)要求定義在一個RolesAuthorizationRequirement對象中,這是一種比較煩瑣的編程方式。另一種推薦的做法是在應(yīng)用啟動的過程中創(chuàng)建一系列通過AuthorizationPolicy對象表示的授權(quán)規(guī)則,并指定一個唯一的名稱對它們進(jìn)行全局注冊,那么后續(xù)就可以針對注冊的策略名稱進(jìn)行授權(quán)檢驗。如下面的代碼片段所示,在調(diào)用AddAuthorization擴(kuò)展方法注冊授權(quán)相關(guān)服務(wù)時,我們利用作為輸入?yún)?shù)的Action對象對授權(quán)策略進(jìn)行了全局注冊。表示授權(quán)規(guī)策略的AuthorizationPolicy對象實際上是對基于角色“Admin”的RolesAuthorizationRequirement對象的封裝,我們調(diào)用AuthorizationOptions配置選項的AddPolicy方法對授權(quán)策略進(jìn)行注冊,并將注冊名稱設(shè)置為“Home”。

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()    .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);}
在呈現(xiàn)主頁的WelcomeAsync方法中,我們依然調(diào)用IAuthorizationService服務(wù)的AuthorizeAsync方法來檢驗用戶是否具有對應(yīng)的權(quán)限,但這次采用的是另一個可以直接指定授權(quán)策略注冊名稱的AuthorizeAsync方法重載(S2802)。
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擴(kuò)展方法進(jìn)行注冊。

...var builder = WebApplication.CreateBuilder();builder.Services    .AddSingleton()    .AddSingleton()    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app    .UseAuthentication()    .UseAuthorization();...

當(dāng)該中間件在進(jìn)行授權(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擴(kuò)展方法時已經(jīng)定義了授權(quán)策略,我們也可以按照如下的方式將策略名稱設(shè)置為AuthorizeAttribute特性大的Policy屬性(S2804)。

[Authorize(Policy = "Home")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!);

如果采用Lambda表達(dá)式來定義終結(jié)點處理器,我們可以按照如下的方式將AuthorizeAttribute特性標(biāo)注在表達(dá)式上。注冊終結(jié)點的各種Map方法會返回一個IEndpointConventionBuilder對象,我們可以安裝如下的方式調(diào)用它的RequireAuthorization擴(kuò)展方法將AuthorizeAttribute特性作為一個IAuthorizeData對象添加到注冊終結(jié)點的元數(shù)據(jù)集合。RequireAuthorization擴(kuò)展方法來有一個將授權(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)閱讀

title="99久久人妻无码精品系列蜜桃|欧美一区二区三区乱码AⅤ|精品国语对白精品自拍视|在线视频免费观看一区|98av国产欧美日韩亚洲欧洲|人妻丰满熟妇av无码区二区三区|强乱中文字幕av一区乱码|亚洲日本一区二区|国产suv一区二区|欧美精品电影一区二区三区|免费无码毛片一区二区app|粉嫩的18在线观看极品精品">