OpenIDConnect是一個身份驗證服務,而Oauth2.0是一個授權框架,在前面幾篇文章里通過IdentityServer4實作了基于Oauth2.0的客戶端證書(Client_Credentials)、用戶名密碼(Password)的授權流程,同時也實作OpenIDConnect的授權碼(Authorization Code)、隱式流程(Implicit)的身份驗證, ???啥?一會兒是授權一會兒是身份驗證,身份驗證與授權傻傻分不清楚??本文就來聊一聊Asp.net core中的身份驗證與授權, 本文主要內容有:
- 身份驗證與授權
- Asp.net core中的身份驗證與授權
- Asp.net core身份驗證及授權的基本原理
- Scheme與身份驗證處理器
- 三個方法Authenticate、Challenge、Forbid
- 兩個中間件AuthenticationMiddleware、AuthorizationMiddleware
- 三個物件HttpContext、ClaimsIdentity、AuthenticationProperties
- Signin與身份資訊載體
- 自主登錄與外部登錄
- Asp.net core身份驗證及授權流程
- Asp.net core中的授權
- 小結
身份驗證與授權
以前寫過一篇asp.net identity的文章(https://www.cnblogs.com/selimsong/p/7828326.html)已經提到過身份驗證與授權的概念,簡單來說身份驗證就是“是誰”的問題,而授權就是“能不能”的問題,一般來說首先需要知道“是誰”,然后再判斷“能不能”, 這里舉個生活中常見的小栗子,鎖是門用來保護門內財產的工具,而隨著科技發展現在有了指紋鎖,指紋鎖的特征是它既可以通過指紋來開鎖,也可以通過鑰匙開鎖,對于指紋開鎖時首先需要錄入指紋并指定一個指紋身份,比如保姆阿姨,首先需要的就是給她錄入指紋,然后允許該指紋在上午6點至晚上10點可以開門,那么最終保姆阿姨在開門時,授權識別指紋,通過指紋匹配到或者說知道是保姆,這里就是身份驗證,如果陌生人進行指紋匹配那么將匹配不到任何身份,但是能否開門還得根據設定的規則,那就是開門時間是否在規定的時間范圍內,滿足條件才能開門,這就是授權, 當然在開門這個問題上還有一個Bug,那就是鑰匙,只要擁有鑰匙,不管是誰都能開門,獲得鑰匙就是獲得授權, 在軟體系統中通常使用的用戶名密碼登錄實際上就是身份驗證功能,用戶登錄后系統就記住這一狀態,后續訪問系統時系統就知道“是誰”在訪問系統,然后因為已經知道是誰,那么就可以根據具體訪問條件來判斷用戶“能不能”訪問資源,這就是授權,Asp.net core中的身份驗證與授權
首先需要再次明確一下Asp.net core是一個Web框架,它本身就具有一些特性,這其中就包括了身份驗證和授權, 在Asp.net core中的身份驗證和授權是通過中間件完成的,而把一個中間件添加到asp.net core的應用程式中一般只需要兩個步驟,第一是對相關中間件所需引數及服務進行配置,第二就是將相應的中間件添加到請求管道中即可, 下圖為基于OpenIDConnect客戶端程式的身份驗證配置:
Asp.net core身份驗證及授權的基本原理
Scheme與身份驗證處理器
Scheme和處理器可以簡單的理解為一個鍵值對,處理器是用于實際處理身份驗證邏輯的代碼,Scheme就是這個處理器的標識,通過Scheme可以直接獲取到相應的處理器,然后通過處理器來完成身份驗證, Scheme是一個重要的概念,因為在asp.net core中它可以添加多個身份驗證處理器,在Asp.net版本中,或者準確來講Owin中我們就提到過一個多重身份驗證的概念(ASP.NET沒有魔法——ASP.NET Identity 的“多重”身份驗證)實際上也就是在一個應用里面添加了多個身份驗證處理器,換句話說就是一個應用程式支持多種身份驗證(登錄)方式,asp.net core中管理多個身份驗證處理器的核心就是基于Scheme,還記得本文上面oidc驗證添加的服務配置代碼嗎,
三個方法Authenticate、Challenge、Forbid
這三個方法是asp.net core身份驗證/授權中的基礎,它們分別代表身份驗證、質疑和禁止,每一個身份驗證處理器都需要實作這三個方法,下面簡單介紹一下這三個方法: Authenticate:- 身份驗證呼叫和核心邏輯,換句話就是證明“是誰”的方法,
- 擬人化來說就是檢查身份證同時與持有人是否匹配的程序,
- 在程式中就是檢查cookie、jwt token、id token等是否有效,以及資訊載體中標記的用戶“是誰”
- 可翻譯為“懷疑/質疑”,實際上就是身份驗證沒有成功后呼叫的方法,
- 擬人化來說就是“我”不知道你“是誰”,但“我”需要知道,所以“我”會問“你是誰?把你的身份證給我看一下?”
- 在程式中一般的程序就是重定向到登錄頁面,通過登錄方式告訴系統“是誰”,對于Api一類沒有UI的程式時,就回傳401狀態碼告知未通過身份驗證,
- 這個方法用于授權,授權失敗時呼叫該方法,
- 這個方法相對簡單,當程式存在UI時,通過UI告知用戶無權限禁止訪問即可,對于Api一類沒有UI的程式時,通過回傳403狀態碼告知無權限,
兩個中間件AuthenticationMiddleware、AuthorizationMiddleware
身份驗證中間件(AuthenticationMiddleware),只做三件事: 1. 處理身份驗證請求,如oidc的由身份驗證服務器完成id_token生成跳轉的/signi-oidc, 2. 處理默認scheme的身份驗證流程, 3. 如果身份驗證通過后將驗證結果的主體資訊(Principal)放到HttpContext中, 主要代碼如下圖(參考:https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs):
三個物件HttpContext、ClaimsIdentity、AuthenticationProperties
首先我們來看看ClaimsIdentity,它實際上是一組Claim的集合,每個Claim代表用戶身份的一個屬性的鍵值對,一組Claim可以表示某一方面的用戶資訊特性,除此之外它還包含是否通過驗證(IsAuthenticated)以及驗證方式(AuthenticationType)等資訊, 下圖為通過oidc身份驗證的ClaimsIdentity資訊,HttpConext物件中包含的User是ClaimsPrincipal(宣告的主體),一個主體里面包含多個ClaimsIdentity資訊:
Signin與身份資訊載體
前面文章詳細講解了身份驗證的相關細節,但唯獨沒說的就是登錄,登錄到底是做了什么事情?在了解登錄之前我們先來了解一個概念“身份資訊載體”,其實也就字面意思,承載身份資訊的物體,在現實生活中我們的身份資訊載體是“身份證”等等實際物品,而在資訊系統中資訊載體就是一段資料,這段資料為了能讓相關程式或者廣大程式所理解,它應該按照具體的協議來創建,資訊系統中常用的身份資訊載體有Cookie以及Jwt(Json web token), Cookie: 我們都知道http是一個無狀態協議,但是大部分時候我們需要它“有”狀態,Cookie作為一專案覽器資料存盤技術,它經常用于存盤一些狀態資訊,用于下一次發起請求的時候服務器能夠了解當前請求的狀態,所以Cookie非常適合作為身份資訊載體,當然asp.net core的基于Cookie身份驗證是這樣做的,將用戶資訊(ClaimsIdentity)加密后存盤到Cookie中,下次從Cookie中獲取資料,解密后獲得用戶資訊并完成身份驗證, Jwt: Jwt是一種基于Json的安全資訊傳輸標準,Jwt因為帶有數字簽名的,可以保證資料完整性,就想我們的身份證一樣不能偽造,所以也很適合作為身份資訊載體, Cookie和Jwt各有特點,可適用于不同的應用場景,如Cookie它本身有域特性,現在的單頁應用程式它會存在跨域問題,而Jwt雖然能保證資料完整,但是它本身不是加密的(但是傳輸程序可以加密,并且生產一般必須加密,如https),所以Jwt中的身份資訊很容易泄漏,所以它比較適合更封閉的客戶端,如服務端與服務端通訊、手機App等, 現在我們再回來聊登錄,登錄實際上就是將身份資訊寫到身份資訊載體的程序,基于Cookie的就寫Cookie,基于Jwt的就頒發Jwt,但是需要注意的是一般jwt由第三方身份驗證服務器頒發,所以應用程式本身是不需要關注的,所以這里主要講講基于Cookie的登錄, 下面我們做一個基于Cookie登錄的小實驗,首先做一個簡單的基于Identity的登錄功能:
設定斷點后,直接訪問登錄頁面進行登錄,在登錄資訊提交后我們可以看到User資訊是空的:
登錄之后仍然沒有用戶資訊:
但是在ResponseHeader的HeaderSetCookie資訊中我們找到了如下資訊:
看到它即將寫入cookie中帶有它創建的身份資訊載體,這個就是登錄生成身份資訊載體的程序,至于登陸后即可訪問保護內容,是因為登錄完成后做了跳轉,跳轉后將攜帶身份資訊發起請求后既可以完成身份驗證,從而可以訪問受保護內容,
注:Identity提供的登錄功能最終也是通過HttpContext的拓展方法通過IAuthenticationService來完成的,具體可參考相關原始碼,這里不在贅述,
https://github.com/dotnet/aspnetcore/blob/main/src/Http/Authentication.Core/src/AuthenticationService.cs
https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs
自主登錄與外部登錄
自主登錄指的是應用程式本身提供了用戶身份核對(用戶名+密碼登錄),然后擁有用戶資訊自主權(應用程式保存了與用戶相關的資訊),最后根據用戶資訊來生成用戶資訊載體的登錄方式,如Asp.net core Identity提供的就是一種自主登錄方式, 外部登錄指的是由第三方程式來對用戶身份核對,并提供相關用戶資訊交由程式本身來生成用戶資訊載體的,或者直接由第三方程式生成用戶資訊載體的方式, 如本系列文章介紹的oidc的身份驗證就是由IdentityServer提供用戶身份核對并提供用戶資訊(UserInfo EndPoint),然后交由客戶端程式來生成身份資訊載體Cookie, 而如果通過IdentityServer直接通過Oauth2.0流程獲得Access Token的方式就相當于由第三方程式生成用戶資訊載體,客戶端直接驗證用戶資訊載體即可完成后續的身份驗證,Asp.net core身份驗證及授權流程
前面內容詳細介紹了Asp.net core身份驗證相關的一些基礎原理,下面就通過一個流程圖來介紹一下完整的身份驗證和授權流程:
從圖中我們可以找到3個主體分別是:瀏覽器、Asp.net core應用程式以及第三方驗證服務,
整個流程的開始可能是通過訪問受保護資源、自主登錄系統或者外部登錄系統開始,但是登錄的目的在于訪問受保護資源,下面就簡單對訪問受保護資源流程進行梳理:
1. 瀏覽器發起受保護資源訪問請求(沒有Cookie).
2. 服務器對請求進行身份驗證,因為沒有Cookie回傳一個失敗結果,
3. 因為驗證結果為失敗,所以沒有ClamsIdentity資訊,賦值到HttpContext.User也為空,
4. 進行授權判斷,因為沒有經過身份驗證,所以呼叫質疑操作(Challenge),由默認的ChallengeScheme決定是自主登錄還是外部登錄,
5. 如果是自主登錄,那么跳轉到應用登錄頁面完成登錄,并根據用戶資訊生成ClaimsIdentity,
5. 如果是外部登錄,那么跳轉到第三方登錄頁面完成登錄,并回到自主應用的回呼地址對第三方回傳的code、id_token及access_token進行處理,并獲取用戶資訊,根據獲取的用戶資訊生成ClaimsIdentity,
6. 系統將ClaimsIdentity資訊生成身份資訊載體(Cookie)并重定向回之前訪問的資源,
7. 重定向后攜帶身份資訊載體訪問受保護資源,如果用戶有權限,那么可訪問資源,如果沒有權限回傳403禁止訪問,
小提示:為什么asp.net core identity生成的UI代碼中,外部登錄執行的核心代碼為ChallegeResult + (provider 和returnUrl)?
Asp.net core中的授權
前面詳細介紹了Asp.net core中的身份驗證,授權僅僅是其中的一環來幫助完成身份驗證,那么Asp.net core中提供了哪些授權機制或者說要如何進行授權呢? Asp.net core及Identity組件提供了簡單的(只要通過身份驗證)、基于角色的、基于宣告(Claim)的、基于策略的授權機制,具體使用方式參考檔案:https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-5.0 另外還給了一個如何實作資料增刪改權限控制的例子: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-5.0 上面這個例子告訴我們授權機制不僅僅局限于授權特性和中間件,我們可以把授權機制融入到我們的業務邏輯中,小結
本篇文章從Asp.Net core介紹了身份驗證和授權的基本概念和原理,通過流程圖的方式展現了Asp.net core身份驗證和授權的流程,最后簡單介紹了授權的相關機制, 現在我們回到文章開頭問的問題為什么IdentityServer4提供的功能中一會兒是身份驗證,一會兒是授權?? 這個問題需要根據主體來看,首先我們看Oauth2.0,它的最終結果是一個Jwt的Bearer Token,這相當于給了你一把鑰匙,使用這個鑰匙你可以打開指定的門,所以它是一個授權, 然后來看看OIDC的授權碼流程,它除了Access Token外實際上關鍵的是Id_token,證實了用戶的身份,這相當于告訴你,用戶是保姆阿姨,解決了“是誰”的問題,所以是身份驗證,知道了是誰,至于開不開門,那是你(客戶端程式)的授權問題, 最后來看看Asp.net core應用程式,在Asp.net core應用程式中不存在獨立的授權,換句話就是沒法單獨使用授權功能,需要身份驗證和授權功能聯合使用,比如Oauth給了一把鑰匙,但是Asp.net core仍要對鑰匙進行驗證,看清楚鑰匙上貼了張三的名字,但很有可能這把鑰匙是李四拿著, 參考: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0 https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-5.0 以及文章中涉及的相關源代碼 本文地址:https://www.cnblogs.com/selimsong/p/14367624.html 從零搭建一個IdentityServer——目錄(更新中...)轉載請註明出處,本文鏈接:https://www.uj5u.com/net/256147.html
標籤:.NET技术
