授權(quán)碼 + PKCE 模式|OIDC & OAuth2.0 認(rèn)證協(xié)議最佳實(shí)踐系列【03】


在上一篇文章中,我們介紹了 OIDC 授權(quán)碼模式(點(diǎn)擊下方圖片查看),本次我們將重點(diǎn)圍繞 授權(quán)碼 + PKCE 模式(Authorization Code With PKCE)進(jìn)行介紹 ,從而讓你的系統(tǒng)快速具備接入用戶認(rèn)證的標(biāo)準(zhǔn)體系。
為什么會(huì)有 PKCE 模式:
PKCE 是 Proof Key for Code Exchange 的縮寫,PKCE 是一種用于增強(qiáng)授權(quán)碼模式安全性的方法,它可以防止惡意應(yīng)用程序通過截獲授權(quán)碼和重定向 URI 來獲得訪問令牌。PKCE 通過將隨機(jī)字符串(code_verifier)和其 SHA-256 哈希值(code_challenge)與授權(quán)請求一起發(fā)送,確保訪問令牌只能由具有相應(yīng) code_verifier 的應(yīng)用程序使用,保障用戶的安全性。
【OAuth 2.0 協(xié)議擴(kuò)展】PKCE 擴(kuò)展協(xié)議:為了解決公開客戶端的授權(quán)安全問題
「面向?qū)ο蟆筽ublic 客戶端,其本身沒有能力保存密鑰信息(惡意攻擊者可以通過反編譯等手段查看到客戶端的密鑰 client_secret, 也就可以通過授權(quán)碼 code 換取 access_token, 到這一步,惡意應(yīng)用就可以拿著 token 請求資源服務(wù)器了)?
「原理」PKCE 協(xié)議本身是對 OAuth 2.0 的擴(kuò)展, 它和之前的授權(quán)碼流程大體上是一致的, 區(qū)別在于在向授權(quán)服務(wù)器的 authorize endpoint 請求時(shí),需要額外的 code_challenge 和 code_challenge_method 參數(shù);向 token endpoint 請求時(shí), 需要額外的 code_verifier 參數(shù)。最后授權(quán)服務(wù)器會(huì)對這三個(gè)參數(shù)進(jìn)行對比驗(yàn)證, 通過后頒發(fā)令牌。
01.授權(quán)碼 + PKCE 模式(Authorization Code With PKCE)
如果你的應(yīng)用是一個(gè) SPA 前端應(yīng)用或移動(dòng)端 App,建議使用授權(quán)碼 + PKCE 模式來完成用戶的認(rèn)證和授權(quán)。授權(quán)碼 + PKCE 模式適合不能安全存儲密鑰的場景(例如前端瀏覽器)
我們解釋下 code_verifier 和 code_challenge
對于每一個(gè) OAuth/OIDC 請求,客戶端會(huì)先創(chuàng)建一個(gè)代碼驗(yàn)證器??code_verifier
code_verifier:在 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 范圍內(nèi),生成 43-128 位的隨機(jī)字符串。
code_challenge:則是對 code_verifier 通過 code_challenge_method 例如 sha256 轉(zhuǎn)換得來的。
用大白話講下就是在認(rèn)證是用戶攜帶的是加密后的 code_challenge ,在用戶認(rèn)證成功通過 code 獲取 Token 時(shí),客戶端證明自己的方式則是把 code_verifier 原文發(fā)送,認(rèn)證中心收到獲取 Token 請求時(shí)通過 code_verifier ?+ code_challenge_method 進(jìn)行轉(zhuǎn)換,發(fā)現(xiàn)最終結(jié)果與 code_challenge 匹配則返回 Token ,否則拒絕。
1.1 整體流程
整體上,有以下流程:
1.用戶點(diǎn)擊登錄。
2.在你的應(yīng)用中,生成 code_verifier 和 code_challenge。
3.?拼接登錄鏈接(包含 code_challenge ) 跳轉(zhuǎn)到 Authing 請求認(rèn)證。
4.?Authing 發(fā)現(xiàn)用戶沒有登錄,重定向到認(rèn)證頁面,要求用戶完成認(rèn)證。
5.?用戶在瀏覽器完成認(rèn)證。
6.?Authing 服務(wù)器通過瀏覽器通過重定向?qū)⑹跈?quán)碼(code)發(fā)送到你的應(yīng)用前端。
7.?你的應(yīng)用將授權(quán)碼 (code) 和 code_verifier 發(fā)送到 Authing 請求獲取 Token.
8.?Authing 校驗(yàn) code、code_verifier 和 code_challenge。
9.?校驗(yàn)通過,Authing 則返回 AccessToken 和 IdToken 以及可選的 RefreshToken。
10.?你的應(yīng)用現(xiàn)在知道了用戶的身份,后續(xù)使用 AccessToken 換取用戶信息,調(diào)用資源方的 API 等

1.2 準(zhǔn)備接入
1.2.1 整體流程
需要先在 Authing 創(chuàng)建應(yīng)用:

配置登錄回調(diào)和登出回調(diào),配置為你實(shí)際項(xiàng)目的地址,我們在這里配置 localhost 用于測試。
若你想匹配多個(gè)登錄/登出回調(diào)
可以使用 ‘*’ 號進(jìn)行通配,登錄/登出回調(diào)可以是如下格式


在協(xié)議配置中,我們勾選 authorization_code 并且使用 code 作為返回類型,如下圖所示:
PKCE 模式使用的是 code_verifier 來換取 Token ,所以需要配置獲取 Token 的方式為 null

1.3 接入測試
1.3.1 所需調(diào)用接口列表
GET? ${host}/oidc/auth 發(fā)起登錄(拼接你的發(fā)起登錄地址)
POST?${host}/oidc/token 獲取 Token
GET ${host}/oidc/me 獲取用戶信息
POST?${host}/oidc/token/introspection 校驗(yàn) Token
POST?${host}/oidc/token 刷新 Token
POST?${host}/oidc/revocation 吊銷 Token
GET ${host}/session/end 登出
1.3.2?Run in Postman所需調(diào)用接口列表
1.3.3 發(fā)起登錄
GET ${host}/oidc/auth
這是基于瀏覽器的 OIDC 的起點(diǎn),請求對用戶進(jìn)行身份驗(yàn)證,并會(huì)在驗(yàn)證成功后返回授權(quán)碼到您所指定的 redirect_uri。
生成 code_challenge 和 code_verifier
在線生成
離線生成
首先,我們要生成一個(gè) code_challenge 和 code_verifier,以下是使用 JavaScript 語言生成 PKCE 所需要的?code_verifier?和?code_challenge?的腳本:
以上代碼使用?jsSHA?庫計(jì)算 SHA-256 哈希值,使用 base64url 編碼將哈希值轉(zhuǎn)換為?code_challenge。你可以將以上代碼復(fù)制到你的 JavaScript 代碼中,并使用?pkce.codeVerifier?和?pkce.codeChallenge?調(diào)用 OAuth 2.0 授權(quán)請求。
舉例
code_verifier 的長度為 43 ~ 128 ,我們生成的是 128 位
發(fā)起登錄地址(瀏覽器中打開)
體驗(yàn)地址
參數(shù)說明

1.3.4 獲取 Token
用戶在 Authing 側(cè)完成登錄操作后, Authing 會(huì)將生成的 code 作為參數(shù)回調(diào)到 redirect_uri 地址,此時(shí)通過 code 換 token 接口即可拿到對應(yīng)的訪問令牌 access_token
請求參數(shù)

請求示例
響應(yīng)示例(成功)
響應(yīng)示例(失?。?/strong>
1.3.5 所需調(diào)用接口列表
此端點(diǎn)是 OIDC 獲取用戶端點(diǎn),可以通過 AccessToken 獲取有關(guān)當(dāng)前登錄用戶的信息。
請求參數(shù)

請求示例
響應(yīng)示例(成功)
響應(yīng)示例(失?。?/strong>
1.3.6 校驗(yàn) Token
此端點(diǎn)接受 access_token、id_token、refresh_token ,并返回一個(gè)布爾值,指示它是否處于活動(dòng)狀態(tài)。如果令牌處于活動(dòng)狀態(tài),還將返回有關(guān)令牌的其他數(shù)據(jù)。如果 token 無效、過期或被吊銷,則認(rèn)為它處于非活動(dòng)狀態(tài)。
access_token 可以使用 RS256 簽名算法或 HS256 簽名算法進(jìn)行簽名。下面是這兩種簽名算法的區(qū)別:
RS256 是使用 RSA 算法的一種數(shù)字簽名算法,它使用公鑰/私鑰對來加密和驗(yàn)證信息。RS256 簽名生成的令牌比 HS256? 簽名生成的令牌更加安全,因?yàn)槭褂?RSA 密鑰對進(jìn)行簽名可以提供更高的保護(hù)級別。使用 RS256 簽名算法的令牌可以使用公鑰進(jìn)行驗(yàn)證,公鑰可以通過 JWK 端點(diǎn)獲取。
HS256 是使用對稱密鑰的一種數(shù)字簽名算法。它使用同一個(gè)密鑰進(jìn)行簽名和驗(yàn)證。HS256 簽名算法在性能方面比 RS256 簽名算法更快,因?yàn)樗褂玫氖菍ΨQ密鑰,而不是使用 RSA 公鑰/私鑰對來簽名和驗(yàn)證。使用 HS256 簽名算法的令牌可以通過 shared secret (應(yīng)用密鑰)進(jìn)行驗(yàn)證。
在實(shí)際應(yīng)用中,RS256 算法更加安全,但同時(shí)也更加消耗資源,如果系統(tǒng)需要高性能,可以選擇 HS256 簽名算法。
驗(yàn)證 Token 分為兩種方式
本地驗(yàn)證與使用 Authing 在線驗(yàn)證。我們建議在本地驗(yàn)證 JWT Token,因?yàn)榭梢怨?jié)省你的服務(wù)器帶寬并加快驗(yàn)證速度。你也可以選擇將 Token 發(fā)送到 Authing 的驗(yàn)證接口由 Authing 進(jìn)行驗(yàn)證并返回結(jié)果,但這樣會(huì)造成網(wǎng)絡(luò)延遲,而且在網(wǎng)絡(luò)擁塞時(shí)可能會(huì)有慢速請求。
以下是本地驗(yàn)證和在線驗(yàn)證的優(yōu)劣對比:

在線校驗(yàn)
需要注意的是,id_token 目前無法在線校驗(yàn),因?yàn)?id_token 只是一個(gè)標(biāo)識,若需要校驗(yàn) id_token 則需要您在離線自行校驗(yàn)
請求參數(shù)

請求示例
校驗(yàn) access_token 響應(yīng)示例(校驗(yàn)通過)
校驗(yàn) access_token 響應(yīng)示例(校驗(yàn)未通過)
校驗(yàn) refresh_token 響應(yīng)示例 (校驗(yàn)通過)
校驗(yàn) refresh_token 響應(yīng)示例(校驗(yàn)未通過)
離線校驗(yàn)
可參考文檔(Authing 開發(fā)者文檔):
我們簡單說下,若您使用離線校驗(yàn)應(yīng)該對 token 進(jìn)行如下規(guī)則的校驗(yàn)
1.格式校驗(yàn)?- 校驗(yàn) token 格式是否是 JWT 格式
2.類型校驗(yàn)?- 校驗(yàn) token 是否是目標(biāo) token 類型,比如 access_token 、id_token、refresh_token
3.issuer 校驗(yàn)?- 校驗(yàn) token 是否為信賴的 issuer 頒發(fā)
4.簽名校驗(yàn)?- 校驗(yàn) token 簽名是否由 issuer 簽發(fā),防止偽造
5.有效期校驗(yàn)?- 校驗(yàn) token 是否在有效期內(nèi)
6.claims 校驗(yàn)?- 是否符合與預(yù)期的一致
示例代碼
下面是一個(gè)示例 Java 代碼,可以用于在本地校驗(yàn) OIDC RS256 和 HS256 簽發(fā)的 access_token
這段代碼使用 Nimbus JOSE+JWT 庫來解析和驗(yàn)證 JWT token。它使用指定的 issuer 和 audience 值對 access_token 進(jìn)行驗(yàn)證,并驗(yàn)證 JWT 中 claims 的格式、類型、簽名、有效期和 issuer。如果發(fā)生任何驗(yàn)證錯(cuò)誤,則將拋出? RuntimeException。使用時(shí)需要傳入對應(yīng)的 JWK URL 和 access_token 進(jìn)行調(diào)用,例如:
這個(gè)示例只校驗(yàn)了 RS256 和 HS256 簽名算法。
1.3.7 刷新 Token
此功能用于用戶 token 的刷新操作,在 token 獲取階段需要先獲取到 refresh_token 。
請求參數(shù)

請求參數(shù)
響應(yīng)示例(成功)
響應(yīng)示例(失敗)
1.3.8 撤回 Token
撤銷 access_token / refresh_token 。
請求參數(shù)

請求示例
響應(yīng)示例(成功)
響應(yīng)示例(失敗)
1.3.9 用戶登出
使用此操作通過刪除用戶的瀏覽器會(huì)話來注銷用戶。
post_logout_redirect_uri?可以指定在執(zhí)行注銷后重定向的地址。否則,瀏覽器將重定向到默認(rèn)頁面
請求參數(shù)

請求示例(瀏覽器訪問)
02.本章總結(jié)
本章我們介紹了 OIDC 授權(quán)碼模式的接入流程以及相關(guān)接口的調(diào)用方式,對于小白來說可能需要整體跑一遍流程才能熟悉,我們也建議你 fork 我們的 postman collection 跑一遍流程,對 PKCE 模式你就基本掌握啦。