.NET CORE 授權
一、三種方式授權
不論使用NET CORE框架的何種授權都必須引入中間件,因為它實現了在管道中對當前請求的鑒權和授權的驗證,在Startup中的Configure中首先加入鑒權和授權的中間件
| 中間件 | 描述 |
|---|---|
| UseAuthentication | 鑒權中間件 |
| UseAuthorization | 授權中間件,基於鑒權 |
1.Scheme 和 Role
基於Cookie 的Scheme授權,就是在授權時檢查下是否存在對應的Scheme,可以使用自定義的Scheme授權,當然此處只是簡單介紹Scheme和Role授權的方法,在選擇使用.NET CORE框架的授權方式時,常用的是Policy的方式,因為Policy的擴展性和可配置性是三種方式中最強的,其實歸根結底Scheme和Role授權方式實現的本質,就是基於Policy的封裝而已,至於是如何實現到后面結合源碼來探究,但是在這之前,要知道如何使用它
-
1.引入中間件並且在IOC容器中注冊基於Scheme授權
//在IOC中注冊 services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = "/Authorization/Index"; options.AccessDeniedPath = "/Authorization/Index"; }); //在Configure中引入中間件 app.UseAuthentication(); app.UseAuthorization(); -
2.在對應的API中加入授權特性,特性的策略名AuthenticationSchemes必須和注冊IOC的authenticationScheme一致
[Authorize(AuthenticationSchemes = "Cookies")] public IActionResult Info() { return View(); } -
3.如果使用角色授權,就需要在登錄時寫入Claim的Role信息,然后在對應的Api接口上使用時加入[Authorize]特性
[AllowAnonymous] public IActionResult Login(string name, string password) { //用戶名密碼不正確直接返回 if (!"Admin".Equals(name) || !"123456".Equals(name)) { return new JsonResult(new{ Result = false,Message = "登錄失敗" }); } var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); //寫入身份信息角色為 claimIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin")); await base.HttpContext.SignInAsync("Cookies", new ClaimsPrincipal(claimIdentity), new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddMinutes(30), }); return new JsonResult(new{ Result = true,Message = "登錄成功"}); } [Authorize(Roles = "Admin")] public IActionResult InfoAdmin() { return View(); }
2.Policy
-
1.在Ioc中注冊2種基於策略的授權
AdminPolicy和UserPolicy,在授權AdminPolicy時會校驗Claim的Role的值是“Admin”,Name的值是“美洋洋”,而且必須包含郵箱,在檢驗UserPolicy時會驗證Claim中是否存在Role這個Key,並且驗證Role的值是"User",如果用戶鑒權通過的信息中完全符合這些規則的要求,那么授權就會通過services.AddAuthorization(options => { options.AddPolicy("AdminPolicy", policyBuilder => policyBuilder //Claim的Role是Admin .RequireRole("Admin") //Claim的Name是美洋洋 .RequireUserName("美洋洋") ////必須有某個Cliam .RequireClaim(ClaimTypes.Email) ); options.AddPolicy("UserPolicy", policyBuilder => policyBuilder.RequireAssertion(context => context.User.HasClaim(c => c.Type == ClaimTypes.Role) && context.User.Claims.First(c => c.Type.Equals(ClaimTypes.Role)).Value == "User") ); }); -
2.我們在控制器中,在需要使用授權的
Action上,標記授權特性並聲明使用Policy的方式,當客戶端訪問Action時管道會自動去檢驗它的合法性,確定是否授權,注意如果Action上同時使用了[AllowAnonymous],那么就會忽略授權檢驗,因為[AllowAnonymous]的優先級是高於[Authorize]的[Authorize(Policy = "AdminPolicy")] public IActionResult AdminPolicy() { return View(); }
二、源碼解讀授權流程
-
1.經過上面幾種簡單的配置基本知道了3種方式的使用方法,在我們去探究查看他的實現源碼之前,我們腦海中應該對它的流程有個大概的構思,如果是我們自己來做這個功能怎么做,一般不管有沒有工作經驗的朋友,基本都能想出解決方案,只是方案的細節差異或者邏輯嚴謹以及擴展度不同而已,大概的思路無非大同小異。
- 1.如果是Policy方式提前定義好授權的規則信息,登錄成功后存儲用戶的各項身份信息。
- 2.反射找到控制器上標記的Authorize特性。
- 3.根據特性上給的授權方式和規則以及對應的值 與 用戶身份信息中的信息比對來得出是否能訪問當前控制器。
-
2.根據上面4點簡單的邏輯可以實現授權,甚至可以說.NET CORE對於授權大致思路也是這么做的,只是他的擴展和可配置以及設計方式是經過詳細策划的,以至於實現的更加完整和合理,對於我們,主要思考的問題有如下幾點。
-
1.什么時候注冊規則?能不能自定義規則?
-
2.什么時候找到控制器標注的特性?
-
3.什么時候對比規則決定是否通過授權?
-
接下來我們帶着我們自己假設性的思路及思考的問題,來查看源碼是如何做到的
1.注冊解析流程
1.授權注冊常用類
-
1.首先我們需要簡單認識一下如下幾個類
類名 描述 PolicyServiceCollectionExtensions 用戶注冊授權服務的擴展類,包含2個AddAuthorization()方法 AuthorizationOptions 用於添加授權時,自定義配置的提供類,他提供了自定義添加策略和查找策略的方法以及存儲的屬性 AuthorizationPolicyBuilder 用於提供用戶配置添加授權規則,再根據規則和Scheme創建AuthorizationPolicy的功能 AuthorizationPolicy AuthorizationPolicy的一個實例,對應一個授權的Policy,它是由AuthorizationPolicyBuilder創建 IAuthorizationRequirement 提供授權規則的接口約束,他的實例Requirement就是具體的授權條件,例如Role授權方式的規則,由它的實例RolesAuthorizationRequirement實現
2.規則源碼解析
-
1.在.NET CORE中授權注入規則是依賴
PolicyServiceCollectionExtensions擴展類來完成的,他包含了2個重載的AddAuthorization(),它的第二個重載包含了一個帶參的AuthorizationOptions類型的無返回值委托,我們將從他開始介紹,如果看過WebApi的源碼應該知道,我們的Application_Start啟動類中,調用的GlobalConfiguration.Configure方法就是這樣的風格及設計,至於如果不知道為何使用這種設計方式的話,應該是沒有搞清楚委托的本質,建議去真正的理解了委托之后再來看,因為委托單單對於NET CORE來說是功不可沒的。
-
2.打開AuthorizationOptions里面,包含了一個值為
AuthorizationPolicy類型的字典PolicyMap和重載的AddPolicy()方法,我們在注冊階段主要介紹PolicyMap和AddPolicy()的第二個重載方法,而PolicyMap就是用於存儲Policy的,我們看到同樣在AddPolicy()時,也將一個無返回值委托作為第二個參數,而委托的參數為AuthorizationPolicyBuilder類型的,而AuthorizationPolicyBuilder的最終目的,就是用來創建AuthorizationPolicy和創建規則的。
-
3.打開AuthorizationPolicyBuilder類,有一個IList
類型的Requirements屬性,他是用於存儲用戶注冊是添加的規則集合,現在看到配置策略的這段代碼是不是瞬間就明白了。 -
- AuthorizationOptions 類中的第二個AddPolicy()方法用於添加一個Policy
-
- AuthorizationPolicyBuilder 類中RequireUserName()方法用於添加規則,最終加入到AuthorizationPolicyBuilder的Requirements屬性中去了
services.AddAuthorization((AuthorizationOptions authorizationOptions) => { authorizationOptions.AddPolicy("CustomPolicy", (AuthorizationPolicyBuilder authorizationPolicyBuilder) => authorizationPolicyBuilder.RequireUserName("懶洋洋")); })
-
-
4.我們看到AuthorizationPolicyBuilder的RequireUserName()方法,他加入的是一個繼承自
IAuthorizationRequirement接口的NameAuthorizationRequirement,它的作用就是一個驗證規則,同理我們根據這個思想,完全可以擴展自己的規則。
3.擴展IAuthorizationRequirement
-
1.創建一個手機號的校驗規則,如果以180和139開頭的手機號,那么就能訪問某一個Api
public class MobilePhoneRequirement : AuthorizationHandler<MobilePhoneRequirement>, IAuthorizationRequirement { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MobilePhoneRequirement requirement) { if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.MobilePhone)) { var phone = context.User.FindFirst(c => c.Type == ClaimTypes.MobilePhone).Value; if (phone.StartsWith("180", StringComparison.OrdinalIgnoreCase) || email.StartsWith("139", StringComparison.OrdinalIgnoreCase)) { context.Succeed(requirement); } else { //context.Fail();//沒成功就留給別人處理 } } return Task.CompletedTask; } } -
2.IOC容器注冊及Action綁定授權
//注冊服務 services.AddSingleton<IAuthorizationHandler, MobilePhoneRequirement>(); //注冊自定義授權策略 services.AddAuthorization((AuthorizationOptions authorizationOptions) => { authorizationOptions.AddPolicy("MobilePhone", policyBuilder => policyBuilder.Requirements.Add(new MobilePhoneRequirement())); }); //控制器中使用 [Authorize(AuthenticationSchemes = "Cookies", Policy = "MobilePhone")] public IActionResult InfoMobilePhone() { return View(); }
2.授權析流程
1.授權注冊常用類
-
1.如下幾個類
類名 描述 AuthorizationMiddleware 授權中間件 IAuthorizationService >DefaultAuthorizationService 授權調用的服務 IAuthorizationPolicyProvider > DefaultAuthorizationPolicyProvider 對於指定的請求,根據規則用於提供對應的Policy IAuthorizationHandlerProvider >DefaultAuthorizationHandlerProvider 提供最終對請求按照規則處理的Handler IPolicyEvaluator > PolicyEvaluator 用於驗證鑒權和授權為最終處理的AuthorizationService提供過渡
2.授權部分解析
-
1.我們想知道最終的請求是如何被授權處理的,所以根據UseAuthorization(),找到授權中間件
AuthorizationMiddleware,在中間件中實現了對請求的授權檢查。

- 1.首先找到當前被調用目標是否
IAuthorizeData標記的信息,其實就是查找被Authorize標記的特性,因為Authorize繼承自IAuthorizeData - 2.將IAuthorizeData 信息利用
AuthorizationPolicy轉換為Policy. - 3.創建一個
IPolicyEvaluator類型的PolicyEvaluator實例 - 4.將
Context和Policy傳入PolicyEvaluator的AuthenticateAsync() 進行鑒權驗證返回一個AuthenticateResult。 - 5.判斷調用目標是否被
AllowAnonymous標記,如果標記則直接跳過授權,進行訪問 - 6.調用PolicyEvaluator的AuthorizeAsync()進行
授權驗證,在方法中將處理轉交由IAuthorizationService處理,返回一個PolicyAuthorizationResult來說明是否授權成功。
- 1.首先找到當前被調用目標是否
-
2.我們進入
IAuthorizationService類型的實例DefaultAuthorizationService中查看

- 1.根據規則,用戶,創建Context 上下文。
- 2.根據上下文在
DefaultAuthorizationHandlerProvider中獲取到繼承自IAuthorizationHandler對應的Requirement集合 - 3.循環執行每一個實現
IAuthorizationHandler的Requirement
