oidc-client.js踩坑吐槽貼

提伯斯發表於2024-05-31

前言

前面選用了IdentityServer4做為認證授權的基礎框架,感興趣的可以看上篇<微服務下認證授權框架的探討>,已經初步完成了authorization-code與implicit的簡易demo(html+js 在IIS部署的站點),並實現了SSO,本想著將Demo遷移到vue工程是輕而易舉,畢竟也沒啥東西,最終拿到access_token,儲存到store裡,跟傳統的jwt基本上一樣,不出意外,意外總是會發生,不然也沒辦法水一篇帖子,玩笑歸玩笑,如果有理解錯誤,或者使用不當的地方,歡迎拍磚,以免誤人子弟
image

環境角色

  • vue2.0 cli
  • .net core 6.0

這裡為什麼不用vue3,嘗試過,最終放棄了,本來就是半吊子的前端水平,vue3裡的vite跟ts,極大的增加了學習成本,包升級後一些莫名其妙的bug,特別是vite,一些bug提示對我來說特別陌生與不友好(專案不報錯,js控制檯報錯,好像是相容之類的問題),總之還是太菜,解決不了,又菜又愛玩
image

準備動手

  • http://localhost:3000/test 測試頁面,用來代替原來的登入頁,用於儲存access_token
  • http://localhost:3000/callback 回撥頁面,用於將code換取access_token
  • http://localhost:3000/logout 退出頁面,用於清除localStorage快取

在IdentityServer4裡,對於前端工程提供了oidc-client.js這個包,在github開源,不過在2021年左右就停止維護了,這裡我收集了兩條跟本次吐槽相關的Issues
https://github.com/IdentityModel/oidc-client-js/issues/1393
https://github.com/IdentityModel/oidc-client-js/issues/1360

OAuth2.0 與 OAuth2.1

嚇了一跳,OAuth2.1是個什麼玩意?來看看GPT的回答
image
原來官方還沒有這個東西,市面上一些對OAuth2.0擴充或者增強的東西,搞了個OAuth2.1,或許只是一個草案,本次吐槽的坑點就是oidc-client.js裡PKCE的使用,PKCE全稱是 Proof Key for Code Exchange, 在2015年釋出, 它是 OAuth 2.0 核心的一個擴充套件協議, 所以可以和現有的授權模式結合使用,比如 Authorization Code + PKCE,東西是好東西,但是強制使用,不與之前的OAuth2.0相容,就要被罵了
image

啥是PKCE?

大白話,就是防止授權碼(code)被壞人攔截,而設計的一個擴充協議,壞人拿到了code也換取不了access_token,它的實現過程也非常簡單,三兩句就能講明白,就不畫圖了,直接上程式碼截圖
三個角色,授權服務,前端工程,後端工程

  1. 認證中心登入,回撥頁面上多了兩個引數

code_challenge_method 簽名方法
code_challenge 簽名

  1. 使用code換取access_token的時候,要多帶一個code_verifier(隨機生成的一個字串) 引數,否則兌換失敗

code_challenge = code_challenge_method(code_verifier)

  1. 協議約定,如果在認證中心生成code的時候,攜帶了code_challenge_method與code_challenge,那麼在使用code兌換
    access_token的時候,必須攜帶code_verifier

如果這還聽不明白,那我舉個古代銀票防偽的例子
張三去錢莊存銀子的時候,跟錢莊做了約定,留下一句暗號(DNF)跟暗號的生成方式(取每個字的首字元)給錢莊,兌換者在用銀票換銀子的時候,錢莊會檢查這張銀票有沒有跟張三做約定,如果有約定,則按照約定讓兌換者提供明文,錢莊用兌換者提供的明文跟張三提供的暗號生成方式,來生成暗號,如果一致則允許兌換銀子
銀票 = code
暗號 = 簽名(code_challenge )
暗號的生成方式 = 簽名方法(code_challenge_method )
明文 = code_verifier(隨機字串)

為啥要噴oidc-client.js裡PKCE的實現?

  1. PKCE關不了
var config = {
    authority: "https://localhost:6201",
    ...
    pkce:false
};

前面Issues有提過,提供了這個配置,但是不生效,總之一定會帶上

  1. 回撥邏輯存在問題
var userManager = new Oidc.UserManager(config);
userManager.signinRedirectCallback().then(function(user) {
    // 登入成功,可以在這裡處理登入後的邏輯,例如重定向到首頁或顯示歡迎資訊
    console.log('使用者已登入:', user);
    window.location.href = '/home'; // 重定向到應用的主頁
}).catch(function(error) {
    // 處理登入失敗的情況
    console.error('登入失敗:', error);
    alert('登入過程中發生錯誤,請重試。');
});

image
image
這是oidc-client.js裡回撥頁面的處理,它自動做了處理,但是它繞過後端程式,直接向認證服務發起,這裡返回400錯誤,是因為該客戶端必須校驗秘鑰,我們一般將秘鑰儲存在後端程式,前端程式請求後端,後端帶上客戶端秘鑰,將請求轉發給認證服務,這才是authorization-code標準的認證流程,也相對安全

  1. 相同的版本,在(html+js)站點下,並沒有啟用PKCE,而在vue-cli工程,預設啟用PKCE

image
image
這點也是我最鬱悶的地方,最開始我懷疑是版本問題導致的,後面我降級到相同的版本,但是結果卻還是一樣,折騰了好久,依舊沒有解決,*******
image

解決辦法

  1. 後端強行關閉PKCE認證

既然vue-cli工程裡發起的獲取code,會攜帶PKCE認證資訊,那我在重定向地址裡,把這兩個引數移除,那就搞定收工了

  1. 啃oidc-client.js原始碼,翻Issues,看看有沒有轉機

先看Issues,把問題關鍵字貼進去搜一下,很遺憾沒有找到有用的東西,原始碼簡單的翻了下,也沒啥幫助

  1. 逆向oidc-client.js,重寫回撥頁面邏輯

既然我們缺code_verifier,那就去找,列印config.userStore,就找到了,這得感謝作者沒有每次都刪除該物件,不然可能就真的芭比Q了,知道了位置,我們直接從Localstorage取,然後根據時間戳取最新的那條記錄,然後提取出code_verifier

function getCodeVerifier() {
  const allKeys = [];
  const items =[];
  for (let i = 0; i < localStorage.length; i++) {
    allKeys.push(localStorage.key(i));
  }
  const pattern = /^oidc\./;
  const oidcKeys = allKeys.filter(key => pattern.test(key));
  for(let i =0;i< oidcKeys.length;i++){
     const str = localStorage.getItem(oidcKeys[i]);
     if(str != null && str != ''){
      const model = JSON.parse(str);
      if(model.created == '' || model.created == null || model.created == undefined){
        continue
      }
      items.push(model)
      localStorage.removeItem(oidcKeys[i])
     }
  }
  const item = items.reduce((nearest, current) => {
    return (nearest.created > current.created) ? nearest : current;
  });
  return item.code_verifier;
}

最終效果

image