IdentityServer網頁登陸-登陸原理


前言

現代程序開發中身份驗證、授權是一件非常非常復雜的事情(各種登陸方式、各種授權需求、各種跳轉跳、各種加解密,搞得得頭皮發麻),因為事情本身復雜,所以沒把這件事理清楚之前,無論你用什么語言、什么框架、什么方式都很難做到既簡單又具有可擴展性。我的想法是既然我自己做不到,那就搞懂身份驗證和授權是咋回事,再學習一套優秀的開源框架。這兩件事其實就是一回事。

之前從asp.net core的源碼開始學起,后來陸續看了asp.net core的身份驗證和授權相關源碼,之前也寫過幾篇相關博客。最近幾天一直在看identityserver源碼,目前對整套東西有個大概了解了,這個過程很痛苦也很有收獲。按個人經驗來說推薦如下學習方式:

  1. 先過一遍蔣老師的《asp.net core 3框架解密》,里面有asp.net core的原理,包括身份驗證和授權的詳解
  2. 過一遍IdentityServer官方文檔
  3. 看IdentityServer源碼

學完你會掌握asp.net core和identiyServer的機制,同時學學大牛的軟件設計思路。

IdentityServer本身支持多種身份驗證和授權的流程,這里只是針對普通mvc程序作為客戶端,集成ids網頁登陸的流程進行說明。搞懂這一個流程,其它流程也差不太遠。這里只是將主體流程,里面涉及到具體步驟已經連接到在上一篇《IdentityServer4種的核心類》中。建議先看看官方文檔的《Protecting an API using Client Credentials》、《Interactive Applications with ASP.NET Core

寫文章時IdentityServer4是4.x版本,下面IdentityServer簡稱ids/ids4,這里主要是根據源碼講原理,如果你只是想簡單使用ids,看官方文檔更合適,可以快速上手。

一、跳轉到登錄頁idsServer/Account/Login(Get請求)

總結:根據請求參數決定顯示哪些第三方登陸

當用戶訪問客戶端(這里的mvc應用,對於ids來說就是客戶端)受保護的頁面時,若沒有登陸則會跳轉到ids的登陸頁面。至於如何實現跳轉的這是客戶端配置的ids身份驗證方案來完成的,不是本篇重點。

具體來說是get請求跳轉到AccountController.Login(returnUrl),returnUrl參數是客戶端配置的ids身份驗證方案生成的,里面包含ids授權需要的重要參數。(注意授權請求的參數並不是直接放在queryString里的,而是放在queryString里的returnUrl這個參數中的)returnUrl的格式如下:

/connect/authorize/callback?
client_id=mvc2&                      --客戶端id好理解
redirect_uri=https://localhost:5003/signin-oidc&  --當ids生成code之后回調客戶端的這個地址,客戶端在這個里會攜帶客戶端密鑰和其它參數再次請求ids,以獲取accessToken/idToken  
response_type=code&                    --希望ids響應的類型
scope=openid profile&                   --客戶端希望請求的資源范圍
code_challenge=MstxPB26Nhnm2HvxVwz7TeFjyvQ6EDzvXp0xH-L7OYk& --目前不曉得啥用,應該是個簽名密鑰
code_challenge_method=S256&                    --目前不曉得啥用,應該是簽名算法
response_mode=form_post&                      --當ids生成code之后以何種方式回調客戶端的“/signin-oidc”,顯然這里會動態生成一個form,然后post提交
nonce=637338663012806688.YWI3MjIxZGItMzlmOC00NmRiLTkz     --隨機字符串,參與簽名計算
MTMtNGFjNDJiMDczNWQzM2Q5NjE4ZjMtZmIwNS00ODAxLWI0ZmQtZ
TAxZDY2NDQyNDlk&                  
state=CfDJ8FuK5UM4huhIoCCo0DRW8LkJjSHkSLmZjj5kYC5BhPOFqfUWAq2Fq6QzWDrqM9W6j2oNMIRmg6Len1JTzHa5uhBh-5Ij0jzrg7vUd5Z-jE1KwRno    --客戶端的一個狀態字段,將來回調客戶端時會原樣攜帶回去
siu3W7hRLc2TKUeviyVd8zoBy1kVj5pmyhNWdahfQLU9lZgPQZBRe7U_itnUUo2S_hO4D1Rm7vq5uJwWTyn_00HNpZCMzVfZyWP2hCPGCRBCJtowq6yhbQ4qD_
L_jPo67MKEjLPEDolw857KkCKPYzu_HlSK1WyiKBTr1-H0yp19p0atpHNOKqtXyLbdY5Lsej0OcQLO99bE1v0LcvRJwyH758ZsbfLRaTUr7O6rI-wXBe6dp3ny
wkFcrW16la09ZPi8rvoUkDQZhhs8qA&

x-client-SKU=ID_NETSTANDARD2_0&
x-client-ver=5.5.0.0

下面是get請求Loigin方法的源碼

1 public async Task<IActionResult> Login(string returnUrl)
2 {
3     var vm = await BuildLoginViewModelAsync(returnUrl);
4     if (vm.IsExternalLoginOnly)
5         return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
6     return View(vm);
7 }

BuildLoginViewModelAsync用來創建一個包含第三發登陸信息的viewModel,用來顯示到登陸頁面上,如何確定要顯示哪些第三方登陸的呢?,由於方法內容稍多,我下面直接說此方法的核心步驟:

  1. 使用交互服務接口IIdentityServerInteractionService驗證returnUrl中的參數,並根據這些參數創建一個表示當前請求的上下文對象AuthorizationRequest 此步驟很常用
  2. 若前端有通過acr_values參數來指定希望通過哪種身份驗證方案,若指定的方案是ids本地用戶登陸,則不顯示三方登陸,否則第三方登陸里只包含客戶端指定的這個第三方登陸
  3. 若客戶端沒有指定,則獲取ids允許的所有身份驗證方案(啟動配置時定義的支持的多種身份驗證方案)
  4. 通過client_id參數從配置中找到客戶端實體,看是否啟用ids本地用戶登陸。
  5. 客戶端若配置了IdentityProviderRestrictions屬性,則與步驟3中做交集來最終確認在頁面要顯示哪些第三登陸
  6. 若請求參數指定了LoginHint參數,則將其作為登陸的默認用戶名

這些步驟都是來設置viewModel的相關屬性,這個viewModel決定視圖頁面要顯示哪些內容

二、Post提交賬號密碼到idsServer/Account/Login

總結:驗證用戶賬號密碼 --> 加密得到的用戶 --> 寫入idsServer域名下的cookie --> 將用戶重定向到"idsServer/connect/authenzation/callback"

用戶輸入完賬號密碼,連同前一步驟的returnUrl(里面包含授權請求的重要參數)Post提交到ids的AccountController.Login這個Action中,下面看看其核心步驟:

  1. 使用交互服務接口IIdentityServerInteractionService驗證returnUrl中的參數,並根據這些參數創建一個表示當前請求的上下文對象AuthorizationRequest 此步驟很常用
  2. 否則若用戶點擊是取消,
    1. 則調用交互服務接口IIdentityServerInteractionServiceDenyAuthorizationAsync在加密cookie中記錄用戶的拒絕授權的操作
    2. 將用戶重定向到"idsServer/connect/authenzation/callback",授權的參數也繼續傳遞過去
  3. 驗證用戶名和密碼,若成功則從配置中獲取用戶實體,同時處罰UserLoginSuccessEvent事件
  4. 使用ids的本地身份驗證方案(cookie)做登陸,寫入加密的用戶信息和相關的身份驗證屬性AuthenticationProperties
  5. 將用戶重定向到"idsServer/connect/authenzation/callback",授權的參數也繼續傳遞過去

三、ids使用AuthorizeCallbackEndpoint處理"callback回調

處理用戶對授權scope的確認、ids中client、resouce和scope的配置最終得出可以授權哪些scope,最后生成code並跳轉到mvc客戶端回調地址

核心步驟:

  1. 通過本地身份驗證方案(cookie)中解密得到用戶信息
  2. 通過用戶授權確認信息存取器IConsentMessageStore獲取用戶授權確認信息ConsentResponse,比如用戶最終允許了哪些scope。由於之前這個信息存儲在加密cookie中的,這時會刪除掉
  3. 通過授權請求驗證器IAuthorizeRequestValidator驗證授權請求參數,然后轉換為表示當前授權請求的AuthorizeRequestValidationResult
  4. 使用授權交互響應構建器IAuthorizeInteractionResponseGenerator檢查用戶是否需要登陸、授權確認信息、將最終授權的resource scope等信息設置到表示當前授權請求的對象中
  5. 通過授權響應構建器IAuthorizeResponseGenerator生成響應,由於這里是ids端的回調,因此此步驟主要是生成存儲code的響應對象AuthorizeResponse
  6. 最后響應時動態創建一個form表單提交到mvc客戶端的“mvc1/oidc-callback”地址

四、客戶端攜帶client_id、client_securet、code等信息向ids請求accessToken/idToken

此步驟是ids提供的客戶端庫來完成的,暫時忽略。我們只要知道此時會攜帶重要的參數:client_id、client_securet、code 去向ids的Authenrize/token端點請求accessToken/idToken就行了

五、ids驗證客戶端密鑰並發放token

客戶端攜帶client_id|、密碼、code和其它參數請求ids的Token端點,ids驗證客戶端密鑰、code等,最后發放accessToken/idToken,核心任務如下:

  1. 請求由ids的TokenEndpoint接管,執行ProcessAsync
  2. 客戶端密鑰驗證器IClientSecretValidator驗證客戶端的密鑰
  3. token請求驗證器ITokenRequestValidator驗證當前請求,主要是對授權類型等參數進行驗證,然后驗證code
  4. 使用token響應構建器ITokenResponseGenerator創建響應TokenResponse
  5. 將TokenResponse包裝為TokenResult,它類似mvc里的actionResult的設計,這個result對象將在ids的中間件中被執行,執行時響應token給mvc客戶端

六、mvc客戶端做本地登陸並跳轉到redirectoryUrl

此步驟是ids提供的客戶端庫來完成的,暫時忽略。我們只要知道

客戶端可以直接拿到用戶標識openid,做本地登陸。如使用asp.net core常用的基於cookie的身份驗證登陸,本質是加密用戶信息和驗證相關的屬性存儲到cookie里

也可以根據標識openid去mvc本地系統的用戶管理模塊中找到匹配的用戶,若沒有則自動或提示用戶注冊或綁定現有賬號。最后以mvc本地的系統用戶做登陸,此時登陸同理使用asp.net core基於cookie的登陸

七、結束

這里聊了下ids作為統一的身份驗證和授權服務里中的網頁登陸,類似於平時集成qq登陸的場景,里面主要描述了這個流程中的主要過程,涉及到具體步驟或類基本都有連接,可以點進去詳細看。沒有介紹基礎知識,建議看源碼學習ids時可以作為參考。這種場景我們平時可能不太會用,下一篇也許會寫個常見的”資源所有者密碼模式“的過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM