前言
前文介紹了Authorization Code flow的基本內容,可以看出其擁有不錯的安全機制。但是仍然存在局限,如果客戶端是運行在服務器上的Web應用程序(這類客戶端稱為機密客戶端)當然是個不錯授權模式,因為很多涉及安全隱患的步驟(如AccessToken)都是通過后端通道由web服務器和授權服務器直接通信的,而不需要經過用戶的瀏覽器或者其他的地方。雖然如此,但是Authorization Code仍然是通過前端通道傳遞的,如果code被泄露,就仍然存在安全隱患,典型的有Cut and pasted code attack,就是一種盜用code的攻擊來獲取用戶的權限。因此,官方更加建議使用“Hybrid Flow”或“PKCE”從而增加安全性,但是官方更加推薦使用PKCE。
Hybrid Flow
先介紹一下ID_Token具體是什么,ID_Token就像我們的身份證或者戶口本,客戶端程序可以通過ID_Token獲取用戶的Claims。ID_Token由三個部分(頭Header,體Body和簽名Signature)組成並且是通過JWT形式呈現。ID_Token中包含的主要內容有iss(Issuing authority)、sub(A unique identifier for the end-user issued by the issuer)、aud、exp、iat、auth_time等。
Hybrid Flow在流程上大致是和Authorization Code flow一樣的,唯一的區別是在完成用戶的身份認證之后,通過authorization endpoint返回的數據不同。而這一步返回的數據是根據我們發送請求時的response_type參數決定的,這使得流程更加靈活。
Hybrid Flow根據response_type的不同,authorization endpoint返回可以分為三種情況。
- response_type = code + id_token ,即包含Authorization Code和identity Code
- response_type = code + token ,即包含Authorization Code和Access Code
- response_type = code + id_token + token,即包含Authorization Code、identity Code和Access Token
可以看到,code都是一定會返回的。如果ID_Token在authorization endpoint和token endpoint都被返回了,那么
- iss和sub的值必須是相同的
- 所有關於身份認證的Claims在兩者中應該都包含
- 但是authorization endpoint返回的Claim數量應該會少一點,這也是出於對隱私的考慮
如果Access Token在authorization endpoint和token endpoint都被返回了,那么
- 那么兩次值可能相同也可能不同
- 這都是取決於這兩個終結點的安全特性
那么到底為什么要使用hybrid flow呢?讓我們的應用程序在前端通道和后端通道都可以接收到分開的token。接下來進行一些簡單的實踐,有了前面AuthorizationCode模式代碼的經驗,編寫Hybrid Flow也是很簡單的。
授權服務器上新增Hybrid的Client
在之前代碼的基礎上,新增一個授權方式是Hybrid的client,這樣就ok了。
創建一個Hybrid流程的MVC客戶端
創建一個新的ASP.NET Core MVC程序,取名為“MVC_Hybrid”,使用Nuget添加IdentityModel和OpenIdConnect的引用。
在Startup.cs中進行配置,代碼和Authorization Code Flow的基本一致,只是我們在配置ResponseType時需要使用Hybrid定義的三種情況之一,具體代碼如下。
隨后在Controller中,和前面介紹Authorization Code時一樣,我們通過擴展方法在授權過程中獲取的幾個token,並顯示到界面上

1 public async Task<IActionResult> Index() 2 { 3 //我們利用拓展方法獲取存下來的Access Token 4 var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); 5 ViewBag.idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken); 6 var client = new HttpClient(); 7 //攜帶上AccessToken 8 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 9 //去請求受保護的api1上的資源 10 var response = await client.GetAsync("http://localhost:5001/identity"); 11 if (!response.IsSuccessStatusCode) 12 { 13 ViewBag.Json = response.ReasonPhrase; 14 } 15 var content = await response.Content.ReadAsStringAsync(); 16 ViewBag.Json = JArray.Parse(content).ToString(); 17 return View(); 18 }
1 @{ 2 ViewBag.Title = "Json"; 3 Layout = "_Layout"; 4 } 5 <p>@ViewBag.idToken</p> 6 <pre>@ViewBag.Json</pre>
以上,准備工作就已經完成了。接下來運行授權服務器和mvc客戶端,運行成功后使用內置的用戶登錄,完成授權后進入獲取到數據。
在整個過程中,我們使用Fiddler抓取請求,可以看到在Authorization Endpoint同時返回了code和id_token。
使用https://jwt.io/解析Authorization EndPoint返回的Id_Token和Token EndPoint返回的id_Token,可以看到其中包含的用戶信息都是一樣的。
關於Hybrid Flow的其他兩種模式也類似,我們只需要在客戶端的修改請求時的ResponseType即可,就不再贅述了。
在使用Hybrid時我們看到授權終結點返回的Id Token中包含at_hash(Access Token的哈希值)和c_hash(Code的哈希值),規范中定義了以下的一些檢驗規則。
- 兩個id_token中的 iss 和 sub 必須相同。
- 如果任何一個 id token 中包含關於終端用戶的聲明,兩個令牌中提供的值必須相同。
- 關於驗證事件的聲明必須都提供。
- at_hash 和 c_hash 聲明可能會從 token 端點返回的令牌中忽略,即使從 authorize 端點返回的令牌中已經聲明。
Proof Key for Code Exchange
在授權過程中,Authorization Code通過前端通道傳遞有被泄露的風險,在Hybrid Flow中Id_Token也在前端通道傳遞的同時也將用戶數據暴露了出來。因此這里介紹的Proof Key for Code Exchange(PKCE) 就是用來降低威脅的一種方法。概括下PKCE參與在授權驗證中的主要流程。
- 客戶端在請求code前隨機生成一段字符串,稱為code_verifier
- 將code_verifier通過加密算法生成加密后的字符串,稱為code_challenge
- 當客戶端在向授權服務器的授權終結點請求code的同時發送code_challenge給授權服務器,同時為了告訴服務器我使用的是什么加密算法,將算法標識放在
- code_challenge_method中;
- 當客戶端使用code向Token終結點請求AccessToken的同時會發送code_verifier
- 授權服務器使用相同的加密算法將code_verifier變換后與之前收到的code_challenge比對,比對成功才會發放Access Token。
再畫一個簡圖直觀的看一下上面的流程。
其實在第二篇文章中其實我們已經看到了這個方法的影子,在Fiddler查看授權請求時我們看到了code_challeng、code_challenge_method和code_verifier,這是因為我們在配置OpenIdConnect時對PKCE的支持是默認開啟的,只不過當時我們沒有在授權服務器端打開驗證。
那么在授權服務器端我們如何開啟使用PKCE驗證呢,也很簡單,在對client進行配置時,只需添加一句配置。
1 AllowedGrantTypes = GrantTypes.Code, 2 RequirePkce = true, //開啟PCKE
根據官方的說法,還是更加推薦使用PKCE。因為涉及到的加密過程都需要在客戶端中實現,相比Hybrid模式實現PKCE的客戶端就十分簡潔,並且也在前端通道中增加其他的響應元素,最主要的還是因為在實現起來,PKCE更加簡單,在ASP.NET Core3開始后已經增加了對PKCE的默認支持,只需簡單的一句設置(RequirePkce = true)就能搞定了。
參考資料:
https://identityserver4.readthedocs.io/en/latest/topics/grant_types.html
https://medium.com/identity-beyond-borders/openid-connect-hybrid-flow-1123bc9461fe
https://tools.ietf.org/html/rfc7636
https://tonyxu.io/zh/posts/2018/oauth2-pkce-flow/