亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

ASP.NET Core按用戶等級(jí)授權(quán)的方法

瀏覽:450日期:2022-06-09 08:34:49

驗(yàn)證和授權(quán)是兩個(gè)獨(dú)立但又存在聯(lián)系的過(guò)程。驗(yàn)證是檢查訪問(wèn)者的合法性,授權(quán)是校驗(yàn)訪問(wèn)者有沒有權(quán)限查看資源。它們之間的聯(lián)系——先驗(yàn)證再授權(quán)。

貫穿這兩過(guò)程的是叫 Claim 的東東,可以叫它“聲明”。沒什么神秘的,就是由兩個(gè)字符串組成的對(duì)象,一曰 type,一曰 value。type 和 value 有著映射關(guān)系,類似字典結(jié)構(gòu)的 key 和 value。Claim 用來(lái)收集用戶相關(guān)信息,比如

UserName = adminAge = 105Birth = 1990,4,12Address = 火星街130號(hào)

ClaimTypes 靜態(tài)類定義了一些標(biāo)準(zhǔn)的 type 值。如用戶名Name,國(guó)家Country,手機(jī)號(hào)MobilePhone,家庭電話HomePhone 等等。你也可以自己定義一個(gè),反正就是個(gè)字符串。

另外,還有一個(gè)ClaimValueTypes 輔助類,也是一組字符串,用于描述 value 的類型。如Integer、HexBinary、String、DnsName 等。其實(shí)所有 value 都是用字符串表示的,ValueTypes 只是基于內(nèi)容本身的含義而定義的分類,在查找和分析 Claim 時(shí)有輔助作用。比如,值是 “00:15:30”,可以認(rèn)為其 ValueType 是 Time,這樣在分析這些數(shù)據(jù)時(shí)可以方便一些。

一般,代碼會(huì)在 Sign in 前收集這些用戶信息。作用是為后面的授權(quán)做準(zhǔn)備。授權(quán)時(shí)會(huì)對(duì)這些用戶信息進(jìn)行綜合評(píng)估,以決定該用戶是否有能力訪問(wèn)某些資源。

回到本文主題。本文的重點(diǎn)是說(shuō)授權(quán),老周的想法是根據(jù)用戶的等級(jí)來(lái)授權(quán)。比如,用戶A的等級(jí)是2,如果某個(gè)URL要求4級(jí)以上的用戶才能訪問(wèn),那么A就無(wú)權(quán)訪問(wèn)了。

為了簡(jiǎn)單,老周就不建數(shù)據(jù)庫(kù)這么復(fù)雜的東西了,直接寫個(gè)類就好了。

public class User{    public string? UserName { get; set; }    public string? Password { get; set; }    /// <summary>    /// 用戶等級(jí),1-5    /// </summary>    public int Level { get; set; } = 1;}

上面類中,Level 屬性表示的是用戶等級(jí)。然后,用下面的代碼來(lái)產(chǎn)生一些用戶數(shù)據(jù)。

public static class UserDatas{    internal static readonly IEnumerable<User> UserList = new User[]    {new(){UserName="admin", Password="123456", Level=5},new(){UserName="kitty", Password="112211", Level=3},new(){UserName="bob",Password="215215", Level=2},new(){UserName="billy", Password="886600", Level=1}    };    // 獲取所有用戶    public static IEnumerable<User> GetUsers() => UserList;    // 根據(jù)用戶名和密碼校對(duì)后返回的用戶實(shí)體    public static User? CheckUser(string username, string passwd)    {return UserList.FirstOrDefault(u => u.UserName!.Equals(username, StringComparison.OrdinalIgnoreCase) && u.Password == passwd);    }}

這樣的功能,對(duì)于咱們今天要說(shuō)的內(nèi)容,已經(jīng)夠用了。

關(guān)于驗(yàn)證,這里不是重點(diǎn)。所以老周用最簡(jiǎn)單的方案——Cookie。

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opt =>{    opt.LoginPath = "/UserLog";    opt.LogoutPath = "/Logout";    opt.AccessDeniedPath = "/Denied";    opt.Cookie.Name = "ck_auth_ent";    opt.ReturnUrlParameter = "backUrl";});

這個(gè)驗(yàn)證方案是結(jié)合 Session 和 Cookie 來(lái)完成的,也是Web身份驗(yàn)證的經(jīng)典方案了。上述代碼中我配置了一些選項(xiàng):

LoginPath——當(dāng) SessionID 和 Cookie 驗(yàn)證不成功時(shí),自動(dòng)轉(zhuǎn)到些路徑,要求用戶登錄。

LogoutPath——退出登錄(注銷)時(shí)的路徑。

AccessDeniedPath——訪問(wèn)被拒絕后轉(zhuǎn)到的路徑。

ReturnUrlParameter——回調(diào)URL,就是驗(yàn)證失敗后會(huì)轉(zhuǎn)到登錄URL,然后會(huì)在URL參數(shù)中加一個(gè)回調(diào)URL。這個(gè)選項(xiàng)就是配置這個(gè)參數(shù)的名稱的。比如這里我配置為backUrl。假如我要訪問(wèn)/home,但是,驗(yàn)證失敗,跳轉(zhuǎn)到 /UserLog 登錄,這時(shí)候會(huì)在URL后面加上 /UserLog?backUrl=/home。如果登錄成功且驗(yàn)證也成功了,就會(huì)跳轉(zhuǎn)回 backUrl指定的路徑(/home)。

這里要注意的是,我們不能把要求輸入用戶名和密碼作為驗(yàn)證過(guò)程。驗(yàn)證由內(nèi)置的CookieAuthenticationHandler 類去處理,它只驗(yàn)證 Session 和 Cookie 中的數(shù)據(jù)是否匹配,而不是檢查用戶名/密碼對(duì)不對(duì)。你想想,如果把檢查用戶名和密碼作為驗(yàn)證過(guò)程,那豈不是每次都要讓用戶去輸入一次?說(shuō)不定每訪問(wèn)一個(gè)URL都要驗(yàn)證一次的,那用戶不累死?所以,輸入用戶名/密碼登錄只在 LoginPath 選項(xiàng)中配置,只在必要時(shí)輸入一次,然后配合 session 和 cookie 把狀態(tài)記錄下來(lái),下次再訪問(wèn),只驗(yàn)證此狀態(tài)即可,不用再輸入了。

LogoutPath 和AccessDeniedPath 我就不弄太復(fù)雜了,直接這樣就完事。

app.MapGet("/Denied", () => "訪問(wèn)被拒絕");app.MapGet("/Logout", async (HttpContext context) =>{    await context.SignOutAsync();});

對(duì)于 LoginPath,我用一個(gè) Razor Pages 來(lái)處理。

@page@using MyApp@using Microsoft.AspNetCore.Authentication@using Microsoft.AspNetCore.Authentication.Cookies@using System.Security.Claims@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers<form method="post">    <style>label{    display:inline-block;    min-width:100px;}    </style>    <div><label for="userName">用戶名:</label><input type="text" name="userName" />    </div>    <div><label for="passWord">密碼:</label><input type="password" name="passWord" />    </div>    <div><button type="submit">登入</button>    </div></form>@functions{    //[IgnoreAntiforgeryToken]    public async void OnPost(string userName, string passWord)    {var u = UserDatas.CheckUser(userName, passWord);if(u != null){    Claim[] cs = new Claim[]    {new Claim(ClaimTypes.Name, u.UserName!),new Claim("level", u.Level.ToString())  //注意這里,收集重要情報(bào)    };    ClaimsIdentity id = new(cs, CookieAuthenticationDefaults.AuthenticationScheme);    ClaimsPrincipal p = new(id);    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, p);    //HttpContext.Response.Redirect("/");}    }}

其他的各位可以不關(guān)注,重點(diǎn)是 OnPost 方法,首先用剛才寫的UserDatas.CheckUser 靜態(tài)方法來(lái)驗(yàn)證用戶名和密碼(這個(gè)是要我們自己寫代碼來(lái)完成的,CookieAuthenticationHandler 可不負(fù)責(zé)這個(gè))。用戶名和密碼正確后,咱們就要收集信息了。收集啥呢?這個(gè)要根據(jù)你稍后在授權(quán)時(shí)要用到什么來(lái)決定的。就拿今天的主題來(lái)講,我們需要知道用戶等級(jí),所以要收集 Level 屬性的值。這里 ClaimType 我直接用“level”,Value 就是 Level 屬性的值。

收集完用戶信息后,要匯總到ClaimsPrincipal 對(duì)象中,隨后調(diào)用HttpContext.SignInAsync 擴(kuò)展方法,會(huì)觸發(fā)CookieAuthenticationHandler 去保存狀態(tài),因?yàn)樗鼘?shí)現(xiàn)了IAuthenticationSignInHandler 接口,從而帶有SignInAsync 方法。

var ticket = new AuthenticationTicket(signInContext.Principal!, signInContext.Properties, signInContext.Scheme.Name);    // 保存 Session   if (Options.SessionStore != null)   {       if (_sessionKey != null)       {   // Renew the ticket in cases of multiple requests see: https://github.com/dotnet/aspnetcore/issues/22135   await Options.SessionStore.RenewAsync(_sessionKey, ticket, Context, Context.RequestAborted);       }       else       {   _sessionKey = await Options.SessionStore.StoreAsync(ticket, Context, Context.RequestAborted);       }       var principal = new ClaimsPrincipal(   new ClaimsIdentity(       new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },       Options.ClaimsIssuer));       ticket = new AuthenticationTicket(principal, null, Scheme.Name);   }  // 生成加密后的 Cookie 值   var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());    // 追加 Cookie 到響應(yīng)消息中   Options.CookieManager.AppendResponseCookie(       Context,       Options.Cookie.Name!,       cookieValue,       signInContext.CookieOptions); ……

----------------------------------------------------------------------------------------

好了,上面的都是周邊工作,下面我們來(lái)干正事。

授權(quán)大體上分為兩種模式:

1、基于角色授權(quán)。即“你是誰(shuí)就給你相應(yīng)的權(quán)限”。你是狼人嗎?你是預(yù)言家嗎?你是女巫嗎?你是好人嗎?是狼人就賦予你殺人的權(quán)限。

2、基于策略。老周覺得這個(gè)靈活性高一點(diǎn)(純個(gè)人看法)。一個(gè)策略需要一定數(shù)量的約束條件,是否賦予用戶權(quán)限就看他能否滿足這些約束條件了。約束實(shí)現(xiàn)IAuthorizationRequirement 接口。這個(gè)接口未包含任何成員,因此你可以自由發(fā)揮了。

這只不過(guò)是按用途來(lái)劃分的,若從類型本質(zhì)上看,就是一堆IAuthorizationRequirement 組合起來(lái)提供給了AuthorizationHandlerContext,AuthorizationHandlerContext 再通過(guò)一堆IAuthorizationHandler 來(lái)處理。最后由IAuthorizationEvaluator 去總結(jié)授權(quán)的結(jié)果。

這里咱們需要的約束條件是用戶等級(jí),所以,咱們實(shí)現(xiàn)一個(gè)LevelAuthorizationRequirement。

public class LevelAuthorizationRequirement : IAuthorizationRequirement {     public int Level { get; private set; }     public LevelAuthorizationRequirement(int lv)     { Level = lv;     } }

授權(quán)處理有兩個(gè)接口:

1、IAuthorizationHandler:處理過(guò)程,一個(gè)授權(quán)請(qǐng)求可以執(zhí)行多個(gè)IAuthorizationHandler。一般用于授權(quán)過(guò)程中的某個(gè)階段(或針對(duì)某個(gè)約束條件)。一個(gè)授權(quán)請(qǐng)求可以由多IAuthorizationHandler 參與處理。

2、IAuthorizationEvaluator:綜合評(píng)估是否決定授權(quán)。評(píng)估一般在各種IAuthorizationHandler 之后進(jìn)行收尾工作。所以只執(zhí)行一次就可以了,用于總結(jié)整個(gè)授權(quán)過(guò)程的情況得出最終結(jié)論(放權(quán)還是不放權(quán))。

ASP.NET Core 內(nèi)置了DefaultAuthorizationEvaluator,這是默認(rèn)實(shí)現(xiàn),如無(wú)特殊需求,我們不會(huì)重新實(shí)現(xiàn)。

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator{    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)=> context.HasSucceeded    ? AuthorizationResult.Success()    : AuthorizationResult.Failed(context.HasFailed? AuthorizationFailure.Failed(context.FailureReasons): AuthorizationFailure.Failed(context.PendingRequirements));}

所以,咱們的代碼可以選擇實(shí)現(xiàn)一個(gè)抽象類:AuthorizationHandler<TRequirement>,其中,TRequirement 需要實(shí)現(xiàn)IAuthorizationRequirement 接口。這個(gè)抽象類已經(jīng)滿足咱們的需求了。

public class LevelAuthorizationHandler : AuthorizationHandler<LevelAuthorizationRequirement>{    // 策略名稱,寫成常量方便使用    public const string POLICY_NAME = "Level";    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LevelAuthorizationRequirement requirement)    {// 查找聲明Claim? clm = context.User.Claims.FirstOrDefault(c => c.Type == "level");if(clm != null){    // 讀出用戶等級(jí)    int lv = int.Parse(clm.Value);    // 看看用戶等級(jí)是否滿足條件    if(lv >= requirement.Level)    {// 滿足,標(biāo)記此階段允許授權(quán)context.Succeed(requirement);    }}return Task.CompletedTask;    }}

在授權(quán)請(qǐng)求啟動(dòng)時(shí),AuthorizationHandlerContext (上下文)對(duì)象會(huì)把所有IAuthorizationRequirement 對(duì)象添加到一個(gè)哈希表中(HashSet<T>),表示一大串正等著授權(quán)處理的約束條件。

當(dāng)我們調(diào)用 Succeed 方法時(shí),會(huì)把已滿足要求的IAuthorizationRequirement 傳遞給方法參數(shù)。在 Success 方法內(nèi)部會(huì)從哈希表中刪除此IAuthorizationRequirement,以表示該條件已滿足了,不必再證。

public virtual void Succeed(IAuthorizationRequirement requirement){    _succeedCalled = true;    _pendingRequirements.Remove(requirement);}

記得要在服務(wù)容器中注冊(cè),否則咱們寫的 Handler 是不起作用的。

builder.Services.AddSingleton<IAuthorizationHandler, LevelAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler, LevelAuthorizationHandler>();builder.Services.AddAuthorizationBuilder().AddPolicy(LevelAuthorizationHandler.POLICY_NAME, pb =>{    pb.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);    pb.AddRequirements(new LevelAuthorizationRequirement(3));});

策略的名稱我們前面以常量的方式定義了,記得否?

  public const string POLICY_NAME = "Level";

AddAuthenticationSchemes 是把此授權(quán)策略與一個(gè)驗(yàn)證方案關(guān)聯(lián),當(dāng)進(jìn)行鑒權(quán)時(shí)順便做一次驗(yàn)證。上述代碼我們關(guān)聯(lián) Cookie 驗(yàn)證即可,這個(gè)在文章前面已經(jīng)設(shè)置了。AddRequirements 方法添加我們自定義的約束條件,這里我設(shè)置的用戶等級(jí)是 3 —— 用戶等級(jí)要 >= 3 才允許訪問(wèn)。

下面寫個(gè) MVC 控制器來(lái)檢驗(yàn)一下是否能正確授權(quán)。

public class HomeController : Controller{    [HttpGet("/")]    [Authorize(Policy = LevelAuthorizationHandler.POLICY_NAME)]    public IActionResult Index()    {return View();    }}

這里咱們用基于策略的授權(quán)方式,所以[Authorize]特性要指定策略名稱。

好,運(yùn)行。本來(lái)是訪問(wèn)根目錄 / 的,但由于驗(yàn)證不通過(guò),自動(dòng)跳到登錄頁(yè)了。

注意URL上的 backUrl 參數(shù):?backUrl=/。本來(lái)要訪問(wèn) / 的,所以登錄后再跳回 / 。我們選一個(gè)用戶等級(jí)為 5 的登錄。

由于用戶等級(jí)為 5,是 >=3 的存在,所以授權(quán)通過(guò)。

現(xiàn)在,把名為 ck_auth_ent 的Cookie刪除。

這個(gè) ck_auth_ent 是在代碼中配置的,還記得嗎?

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opt =>{    opt.LoginPath = "/UserLog";    opt.LogoutPath = "/Logout";    opt.AccessDeniedPath = "/Denied";    opt.Cookie.Name = "ck_auth_ent";    opt.ReturnUrlParameter = "backUrl";});

現(xiàn)在咱們找個(gè)用戶等級(jí)低于 3 的登錄。

登錄后被拒絕訪問(wèn)。

到此為止,好像、貌似、似乎已大功告成了。但是,老周又發(fā)現(xiàn)問(wèn)題了:如果我一個(gè)控制器內(nèi)或不同控制器之間有的操作方法要讓用戶等級(jí) 3 以上的用戶訪問(wèn),有些操作方法只要等級(jí)在 2 以上的用戶就可以訪問(wèn)。這該咋整呢?有大伙伴可以會(huì)說(shuō)了,那就多弄幾個(gè)策略,每個(gè)策略代表一個(gè)等級(jí)。

builder.Services.AddAuthorizationBuilder()    .AddPolicy("Level3", pb =>    {pb.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);pb.AddRequirements(new LevelAuthorizationRequirement(3));    })    .AddPolicy("Level5", pb =>    {pb.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);pb.AddRequirements(new LevelAuthorizationRequirement(5));    });

是的,這樣確實(shí)是可行的。不過(guò)不夠動(dòng)態(tài),要是我弄個(gè)策略從 Level1 到 Level10 呢,豈不要寫十個(gè)?

官方有個(gè)用 Age 生成授權(quán)策略的示例讓老周獲得了靈感——是的,咱們就是要?jiǎng)討B(tài)生成授權(quán)策略。需要用到一個(gè)接口:IAuthorizationPolicyProvider。這個(gè)接口可以根據(jù)策略名稱返回授權(quán)策略,所以,咱們可以拿它做文章。

public class LevelAuthorizationPolicyProvider : IAuthorizationPolicyProvider{    private readonly AuthorizationOptions _options;    public LevelAuthorizationPolicyProvider(IOptions<AuthorizationOptions> opt)    {_options = opt.Value;    }    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()    {return Task.FromResult(_options.DefaultPolicy);    }    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()    {return Task.FromResult(_options.FallbackPolicy);    }    public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)    {if(policyName.StartsWith(LevelAuthorizationHandler.POLICY_NAME,StringComparison.OrdinalIgnoreCase)){    // 比如,策略名 Level4,得到等級(jí)4    // 提取名稱最后的數(shù)字    int prefixLen = LevelAuthorizationHandler.POLICY_NAME.Length;    if(int.TryParse(policyName.Substring(prefixLen), out int level))    {// 動(dòng)態(tài)生成策略AuthorizationPolicyBuilder plcyBd = new AuthorizationPolicyBuilder();plcyBd.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);plcyBd.AddRequirements(new LevelAuthorizationRequirement(level));// Build 方法生成策略return Task.FromResult(plcyBd.Build())!;    }}// 未處理,交由選項(xiàng)類去返回默認(rèn)的策略return Task.FromResult(_options.GetPolicy(policyName));    }}

這樣可以根據(jù)給定的策略名稱,生成與用戶等級(jí)相關(guān)的配置。例如,名稱“Level3”,就是等級(jí)3;“Level5”就是等級(jí)5。

于是,在配置服務(wù)容器時(shí),我們不再需要AddAuthorizationBuilder 一大段代碼了,直接把LevelAuthorizationPolicyProvider 注冊(cè)一下就行了。

builder.Services.AddSingleton<IAuthorizationHandler, LevelAuthorizationHandler>();builder.Services.AddTransient<IAuthorizationPolicyProvider, LevelAuthorizationPolicyProvider>();builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opt =>……

然后,在MVC控制器上咱們就可以666地玩了。

public class HomeController : Controller {     [HttpGet("/")]     [Authorize(Policy = $"{LevelAuthorizationHandler.POLICY_NAME}3")]     public IActionResult Index()     { return View();     }     [HttpGet("/music")]     [Authorize(Policy = $"{LevelAuthorizationHandler.POLICY_NAME}2")]     public IActionResult Foo() => Content("2星級(jí)用戶擾民音樂(lè)俱樂(lè)部");     [HttpGet("/movie")]     [Authorize(Policy = $"{LevelAuthorizationHandler.POLICY_NAME}5")]     public IActionResult Movies() => Content("5星級(jí)鬼畜影院"); }

這樣一來(lái),配置不同等級(jí)的授權(quán)就方便多了。

到此這篇關(guān)于ASP.NET Core按用戶等級(jí)授權(quán)的文章就介紹到這了,更多相關(guān)ASP.NET Core授權(quán)內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!

標(biāo)簽: ASP.NET
主站蜘蛛池模板: 外国一级黄色 | 精品一区二区三区在线视频 | 米奇精品一区二区三区在线观看 | 国产2021成人精品 | 在线观看欧美日韩 | 又粗又硬的aaaaaaa毛片 | 69国产成人综合久久精品91 | 婷婷 综合 | 乡下女色又黄一级毛片 | 麻豆成人传媒一区二区 | 丝袜美足国产一区在线 | 国产精品一区伦免视频播放 | 国产黄色三级网站 | 年轻人www影视 | 日韩一区二区不卡中文字幕 | 毛片在线视频在线播放 | 碰碰碰免费公开在线视频 | 国产大陆精品另类xxxx | 极品专区高清在线 | 农村高清性色生活片 | 亚洲精品一区二三区在线观看 | 激情综合网婷婷 | 伊人久久综在合线亚洲91 | 国产在线一区二区三区在线 | 色综合久久久久久久久五月性色 | 欧美特黄aaaaaaaa大片 | 99久久精品国产高清一区二区 | 亚洲精品人成网在线播放影院 | 日本特级aⅴ一级毛片 | 一级黄色片在线观看 | 综合久久99 | 五月综合色婷婷 | 免费一级黄色录像 | 黄色在线观看国产 | 亚洲精品人成网在线播放蜜芽 | 久久综合成人网 | 成人在线观看视频免费 | 国产99欧美精品久久精品久久 | 一级黄色录像在线观看 | 午夜在线精品不卡国产 | 亚洲国产日韩在线人成蜜芽 |