單點(diǎn)登錄(SSO),前端需要考慮些什么?

前言

最近在做單點(diǎn)登錄的前端適配工作,包括移動(dòng)端、小程序、web。也許一些人還沒有接觸過單點(diǎn)登錄,接下來我將以keycloak為例進(jìn)行探討。首先介紹兩個(gè)概念:

單點(diǎn)登錄(Single Sign On),簡稱為 SSO,是比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)。(百度百科)

Keycloak是一個(gè)為瀏覽器和RESTful Web服務(wù)提供 SSO 的集成。

聲明:本文僅代表個(gè)人觀點(diǎn)

Refresh_token

如果做過微信開發(fā)的人應(yīng)該看到過refresh_token這個(gè)概念。我就直接引用官方的解釋

refresh_token由于access_token擁有較短的有效期,當(dāng)access_token超時(shí)后,可以使用refresh_token進(jìn)行刷新,refresh_token有效期為30天,當(dāng)refresh_token失效之后,需要用戶重新授權(quán)。(微信開放文檔)

簡單介紹一下,在token即將過期時(shí),通過refresh_token來拿到最新的token

在接下來的介紹中也會(huì)用到refresh這個(gè)概念。

流程圖

先來看一下整體的流程圖,如果不是很明白,可以先往下看,之后再翻上來看就容易理解了


keycloak單點(diǎn)登錄前端適配流程圖

先來想幾個(gè)問題

  1. 首次登錄如何處理?
  2. 之前登錄過如何處理?(考慮關(guān)掉后和重新打開的間隔時(shí)間)
  3. 用戶在使用當(dāng)中,網(wǎng)絡(luò)斷了,然后幾分鐘網(wǎng)好了如何處理?(比如過隧道)
  4. 定時(shí)器選擇setInterval還是選擇setTimeout?(考慮使用過程中,token過期的時(shí)間是否可能在服務(wù)端被修改)

問題1和問題2我們可以在流程圖中找到答案;

問題3(考慮用戶體驗(yàn)):在過隧道的時(shí)候,假如正好是定時(shí)器請(qǐng)求刷新token的時(shí)間,這時(shí)候刷新token肯定會(huì)失敗,那也不能直接就讓定時(shí)器斷掉,而是多次嘗試重新連接,直到refresh_token也過期。

問題4:考慮到token過期的時(shí)間會(huì)被在服務(wù)端更改,所以setTimeout是更好的選擇

核心代碼

token檢查,我們需要考慮超時(shí)時(shí)間(我們的技術(shù)棧是vue)

// 返回值為過期時(shí)間
function checkTokenExpire(type: string) {
  let kcData: any = localStorage.getItem('keycloak')
  if (kcData) kcData = JSON.parse(kcData)

  if (!kcData || !kcData[type]) return -1

  const TokenParsed = kcData[type]

  if (!TokenParsed.exp) return -1

  const exp = TokenParsed.exp * 1000 - 15000

  return exp - now()
}
// token過期的時(shí)間
export function tokenNotExpire() {
  return checkTokenExpire('tokenParsed')
}
// refresh_token過期的時(shí)間
export function refreshTokenNotExpire() {
  return checkTokenExpire('refreshTokenParsed')
}

刷新token,這里會(huì)考慮到“過隧道”的情況

export async function refreshToken() {
  try {
    const refreshed = await store.state.keycloak.updateToken(-1)

    if (refreshed) {
      const kc = await initKc()

      localStorage.setItem('keycloak', JSON.stringify(kc))
      const exp = kc.tokenParsed?.exp
      if (exp) return exp * 1000 - 15000 - now()
    }
  } catch (error) {
    if (refreshTokenNotExpire() > 0) {
      setInteralToken(5000, refreshToken)
    }
  }
}

檢查token過期檢查策略

export async function tokenCheckStrategy() {
  // t, rt為有效期(毫秒)
  const t = tokenNotExpire()

  if (t <= 0) {
    const rt = refreshTokenNotExpire()
    if (rt <= 0) return false

    // 如果refresh_token沒有過期,通過refresh_token拿到access_token, 并且開啟刷新token定時(shí)器
    const tokenExp = await refreshToken()
    if (tokenExp) {
      setInteralToken(tokenExp, refreshToken)
      return true
    }
    return false
  }
  setInteralToken(t, refreshToken)
  return true
}

定時(shí)器需要在得到有效數(shù)據(jù)才能繼續(xù)

export function setInteralToken(exp: number, refreshTokenFn: Function) {
  if (exp > 0) {
    setTimeout(async () => {
      const t = await refreshTokenFn()
      setInteralToken(t, refreshTokenFn)
    }, exp)
  }
}

總結(jié)

渣渣水平,歡迎點(diǎn)贊討論

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容