前端小白初識單點登錄

這是前端切圖仔寫的第一篇文章,因爲才疏學淺,內容可能過於膚淺與沒什麼學習價值,就當紀錄事情好了。前端

前幾天接到貴公司前端組長的一個需求,爲了實現多個域名的登錄,將目前的登錄模塊拆分出來,作成單點登錄。react

  • 本篇涉及的內容有:
    • 微信的受權流程
    • 具體的代碼實現
    • 遇到的問題與小結

微信的受權流程

1.跳轉到微信的受權頁面,若用戶贊成受權,則跳轉至指定的redirect_uri並帶上code參數webpack

2.將code傳給後端換取網頁受權的access_tokenios

3.後端獲取用戶信息(分爲兩種,一種是靜默受權獲取用戶的openid,一種是網頁受權獲取用戶的基本信息)web

4.根據用戶的token是否失效來判斷用戶是否須要再次登錄axios

以上流程只針對該項目,詳細請參考官方文檔微信網頁受權小程序

具體的代碼實現

因爲這個單點登錄代碼很少,實現的功能點也少,因此簡單地搭了個webpack。後端

項目代碼中包含小程序跳轉與其餘一些業務需求,但對這篇文章可有可無,故刪減api

目錄結構以下 跨域

Alt text

下面就登錄受權流程來講明各個目錄的代碼

可能有同窗注意到上面有兩個獲取code的操做,這是爲何呢?由於貴公司的項目爲知識付費,用戶可在多個公衆號上面獲取知識,那麼怎麼判斷在不一樣公衆號聽課的用戶爲同一個呢?

是這樣的。用戶在不一樣公衆號登錄會有不一樣的openid。那若是用戶只在一個公衆號登錄不就能夠惟一肯定用戶了嗎?嘻嘻,可真是個小機靈鬼。因此get_code_1就是獲取用戶在用於惟一標識用戶的公衆號登錄的code,而get_code_2纔是獲取用戶在目前公衆號登錄的code。

get_code_1:跳轉到受權頁面,獲取用戶在用於惟一標識用戶的公衆號登錄的code1。因爲只須要獲取openid,故爲靜默受權。

const wurl = require('wurl') // 解析連接參數的庫
const tx_appid = 用於標識用戶的公衆號的appid
const href = location.href
const code = wurl('?code', href)
let no_auth_host, appid

function handleParams () {
  no_auth_host = wurl('?no_auth_host', href)
  appid = wurl('?appid', href)
  window.localStorage.setItem('no_auth_host', no_auth_host)
  // no_auth_host字段爲登錄成功後跳轉的域名
  window.localStorage.setItem('appid', appid)
  // 目前公衆號的appid,用於獲取code2
}

if (window.localStorage.getItem('callback-accessed')) {
  window.localStorage.removeItem('callback-accessed')
}
// callback-accessed字段爲了不用戶回退到登錄頁面,具體用法在callback部分講

// 已經拿到code1
if (code) {
  handleParams()
  location.replace(`${location.origin}/tlogin1/_get_code2/?code1=${code}`)
} else {
  location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${tx_appid}&redirect_uri=${encodeURIComponent(href)}&response_type=code&scope=snsapi_base#wechat_redirect`)
}
複製代碼

get_code_2:跳轉到受權頁面,獲取用戶目前公衆號登錄的code2。因爲須要獲取用戶信息,故爲網頁受權。

const wurl = require('wurl')
const href = location.href
const code1 = wurl('?code1', href)
const appid = window.localStorage.getItem('appid')

function getCode2Redirect () {
  return encodeURIComponent(`${location.origin}/tlogin1/_callback/?code1=${code1}`)
}

location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${getCode2Redirect()}&response_type=code&scope=snsapi_userinfo&component_appid=wxa56b4f5c89803c1f#wechat_redirect`)
複製代碼

callback:將code一、code2傳給後端換取登錄憑證access_code

import { wxConfig } from '../service'
const wurl = require('wurl')
const axios = require('axios')
const href = location.href
const code1 = wurl('?code1',href)
const code2 = wurl('?code', href)
const no_auth_host = window.localStorage.getItem('no_auth_host')

window.addEventListener('pageshow', (e) => {
  if (e.persisted) {
    // ios的微信回退
    // 回退會致使微信sdk的config出問題,因此reload
    location.reload()
  } else {
    // 微信sdk配置
    wxConfig()
    main()
  }
})

function fetchAccessCode () {
  axios.get('/api/account/auth/unique_weixin_mp_code', {
    params: {
      code1: code1,
      code2: code2,
    }
  })
    .then(res => {
      const data = res.data.data
      if (res.data.code === 0) {
        window.localStorage.setItem('callback-accessed', 1)
        // 獲取access_code成功,本地存儲callback-accessed
        location.assign(`${no_auth_host}/auth/exchange_token?access_code=${data.access_code}`)
        // 爲了安全,跳轉到原先域名的/auth/exchange_token,用access_code換取token,
      }
    })
    .catch(err => {
      alert('系統繁忙,請稍後再試' + err.message)
      window.wx.ready(() => {
        window.wx.closeWindow()
      })
      window.wx.closeWindow()
    })
}

function main () {
  const callback_accessed = window.localStorage.getItem('callback-accessed')
  if (callback_accessed) {
    // 用戶回退到登錄頁面,此時無需再走什麼流程,直接關閉頁面便可
    console.log('will close window...')
    window.wx.ready(() => {
      window.wx.closeWindow()
    })
    window.wx.closeWindow()
    return
  }
  fetchAccessCode()
}

複製代碼

service:service裏面是全局配置函數。這裏只說明微信sdk的配置

const APP_ID = window.localStorage.getItem('appid')

export function wxConfig() {
  return axios.get('/api/weixin/jssdk_config', {
    params: {
      url: location.href,
      appid: APP_ID,
    },
  })
    .then(res => {
      const _config = res.data.data
      window.wx.config({
        debug: false, // debug設爲true可彈出一些配置信息
        appId: APP_ID,
        timestamp: _config.timestamp,
        nonceStr: _config.nonceStr,
        signature: _config.signature,
        jsApiList: ['closeWindow'],
      })
    })
}
複製代碼

更多關於微信sdk配置,請參考官方文檔微信JS-SDK說明文檔

遇到的問題與小結

微信JSAPI:'permission denied'錯誤

當用戶登錄成功跳轉到別的域名後,點擊微信的回退按鈕,這時回退到登錄頁面而且無需進行任何操做,調用微信的closeWindow便可。但在調用時出現了'permission denied'錯誤。

官方對於該錯誤的說明是:該公衆號沒有權限使用這個JSAPI,或者是調用的JSAPI沒有傳入config的jsApiList參數中(部分接口須要認證以後才能使用)。

由於目前正在運行的項目用公衆號A調用該接口沒有問題,故排除第一個可能。 又因代碼確實將'closeWindow'傳入jsApiList。故排除第二個可能。

那麼??問題出在哪?

個人猜測:

  1. 後端給的用於wx.config的簽名失效? 打開config的debug模式彈出config:ok,故排除
  2. wx.config與api調用姿式有誤? 在第一次進入callback頁面可成功調用'closeWindow',而回退調用'closeWindow'失敗,故排除,鎖定問題應該出在微信回退不刷新上
  3. 使用react的push後調用sdk的接口經常失效,是否與此相似? 當走到回退時,從新加載頁面,成功! emmmm,說明一個問題,凡調用微信sdk的api,最好仍是用刷新頁面的location跳轉

用青花瓷代理時,https接口沒法調用

實際上是這樣的,像青花瓷這種抓包工具,默承認以抓http的請求,若是須要抓取https的請求,須要安裝證書。安裝教程網上有不少哦~

後記:爲了不網頁跨域名再次登錄產生的用戶體驗不友好,在對某些跳域名頁面,採用連接上帶上token參數,又爲了不token泄露,使用base64對其加密。具體實現邏輯爲:

加密

  • 1.將token、過時時間(當前時間+30min)各自用base64加密,並用|拼接
const conbine = `${window.btoa(token)}|${window.btoa(now + expires)}`
複製代碼
  • 2.再將拼接完的字符串加鹽、base64加密
window.btoa(conbine).replace(/=/g, this.salt)
複製代碼

解密,即加密過程的逆序

相關文章
相關標籤/搜索