基於Taro框架的微信小程式JWT授權登入方案

Chris威發表於2019-03-01

背景

小程式這兩年可謂是風風火火,已經涉及到了行業的方方面面,作為一枚前端碼農,為了緊跟時代的步伐,滿足市場的需要,樓主也步入了小程式的坑中,一探究竟,從最初的原生開發,過渡到WePY的引入,又轉到了mpvue的框架,最近又準備踩踩Taro的坑 了~

之所以從mpvue框架遷移到Taro框架,一是由於該框架是京東凹凸實驗室開發維護的,目前來看比較活躍,更新迭代速度比較快,質量上有一定的保障;二是該框架支援多端統一,一勞永逸,為後面擴充H5等其他業務奠定基礎~

因業務需求,我們採用JWT進行身份鑑權,所以才有了這篇JWT授權登入 解(cai)決(keng)方(zhi)案(nan),如果對JWT還不是很瞭解的小夥伴,請猛戳這裡~

ok,不廢話了,直接進入你們最想看到程式碼環節~

正文

關於Taro的安裝的使用,我這裡就不再贅述了,具體的步驟可以參考官網給出的教程,我們直接來看看授權登入這一塊~

FBI WARNING : 本文相關的程式碼是跟樓主目前公司的有些業務場景有關,如有不同,請自行更改即可~

初始化Taro專案時,我們需要選擇使用Redux模版,進行狀態管理,然後默默等待依賴安裝完成,通過npm run dev:weapp命令就可以啟動我們的小程式服務了~

接下來跟著我的步驟走下去~

step1

首先,我們需要使用到Redux定義一些app相關的資料,方便我們後面的判斷和使用~

Redux三步走,reducerconstantaction 我這裡就直接貼程式碼了~

  • constants目錄
//constants  app.js

//更改app登入狀態
export const CHANGE_APP_ON_LAUNCH = 'change_app_on_launch';

//寫入授權資訊
export const INSERT_AUTHORIZE = 'insert_authorize';

複製程式碼
  • actions目錄
// actions app.js

import * as constants from '../constants/app'

//更改登入狀態
export const changeAppOnLaunch = () => ({
    type : constants.CHANGE_APP_ON_LAUNCH
})

//寫入請求token
export const insertToken = (authorize) => ({
    type : constants.INSERT_AUTHORIZE ,
    authorize
})

複製程式碼
  • recuders目錄
import * as constants from '../constants/app'

const INITIAL_STATE ={
	//請求介面域名地址
	baseURL : 'https://xx.xxx.xx/' ,
	//應用首次載入
	appOnLaunch : true ,
	//請求token
	authorize : ''
}

export default function app( state = INITIAL_STATE , action ){
    switch (action.type){
    	case constants.CHANGE_APP_ON_LAUNCH :
            return {
            	...state ,
            	appOnLaunch : false
            };
    	case constants.INSERT_AUTHORIZE :
            return {
            	...state ,
            	authorize : action.authorize
            };
    	default :
            return state;
    }
}
複製程式碼

最後不要忘記將其通過 combineReducers 方法合併到 Redux 裡面去~

為了方便後面的使用,這裡我在入口檔案 app.js 中將 store 直接掛在到了 Taro 物件上,這樣我們也就可以在後面通過Taro.$store這種方式直接獲取進行操作處理,即:

 //app.js
 
 componentDidMount() {
    //將redux狀態掛載到 Taro 物件上,方便使用
    Taro.$store = store;
}
複製程式碼

step2

我們在src建立一個utils資料夾,用來存放我們一些公用的方法之類的檔案~

基於Taro框架的微信小程式JWT授權登入方案

由於微信小程式的限制,我們無法得知使用者的登入狀態是否過期,所以我們必須要通過wx.checkSession 來介面檢測當前使用者登入態是否有效,做對應的邏輯處理~

因此,我們建立一個auth.js檔案,用來微信登入,獲取獲取授權資訊,其流程可以參考官方的這張時序圖!

基於Taro框架的微信小程式JWT授權登入方案

我們的JWT就是上面圖所提到的自定義登入狀態,它一般是長這樣的

基於Taro框架的微信小程式JWT授權登入方案

其中exp是令牌過期時間,iat令牌簽發時間,nbf令牌生效時間,token是使用者狀態,包含使用者的身份資訊,我們主要會用到exptoken 兩個欄位,用來判斷令牌是否過期和傳送請求攜帶使用者身份~

因此,我的做法是,在使用者進入小程式的時候通過Taro.checkSession(),檢查session_key是否過期,如果過期需要通過 Taro.login()重新登入進行授權,獲取JWT授權資訊,否則就判斷使用者的令牌是否過期,如果過期就要重新重新整理令牌,重新獲取,其程式碼如下~

// auth.js

import Taro from '@tarojs/taro'
import { insertToken , changeAppOnLaunch } from '../actions/app'
//獲取資料
export default class Auth {
//app授權
static appCheckAuth(){
    return new Promise(function (resolve) {
    	const state = Taro.$store.getState();
    	//如果有授權資訊
    	if( Auth.checkAuth() && !state.app.appOnLaunch ){
            //直接返回
            resolve(true);
    	}else{
            //判斷session_key是否過期
            Taro.checkSession().then(async ()=>{
                //未過期檢查token是否有效
                if( !Auth.checkAuth() ){
                    //判斷是否 token 請求成功
                    let flag = await getAuthToken();
                    if( flag ) {
                        //更新app狀態
                        Taro.$store.dispatch(changeAppOnLaunch());
                        resolve(true);
                    }else{
                      //提示
                        Taro.showToast({
                        title : '獲取授權資訊失敗' ,
                        icon : 'none' ,
                        mask : true
                        })
                    }
                }else{
                    //更新app狀態
                    Taro.$store.dispatch(changeAppOnLaunch());
                    //token 沒有過期,直接返回
                    resolve(true);
                }
            }).catch(async (err)=> {
            	console.log(err);
            	let flag = await getAuthToken();
            	//判斷是否 token 請求成功
            	if( flag ) {
                    //更新app狀態
                    Taro.$store.dispatch(changeAppOnLaunch());
                    resolve(true);
            	}else{
                    //提示
                    Taro.showToast({
                    	title : '獲取授權資訊失敗' ,
                    	icon : 'none' ,
                    	mask : true
                    })
            	}
            })
        }
    })
}
    
    // 檢查令牌是否有效 true--> 有效  false--> 無效
    static checkAuth(){
        const state = Taro.$store.getState();
        //從快取讀取授權資訊
        let authorize = state.authorize || Taro.getStorageSync('authorize') || {},
            expiryTime = 0,
            nowTime = ~~(Date.now() / 1000);
    
    	if (authorize.exp) {
            expiryTime = authorize.exp;
    	}
    
    	return expiryTime - nowTime > 300;
    }

    //獲取token
    static getToken(){
    	const state = Taro.$store.getState();
    	let authorize = state.authorize || Taro.getStorageSync('authorize');
    	return authorize.token;
    }
}

//授權使用者 token
async function getAuthToken(){
	const state = Taro.$store.getState();
	//login
	let res = await Taro.login();
	//獲取token
	let response = await Taro.request({
            url : `${state.app.baseURL}api/xxx/xxx` ,
            data : {
            	code : res.code 
            } ,
            method : 'POST'
	})
    //判斷是否成功
    if( response.data.data && response.data.data.authorize ){
        //寫入token
        let authorize = response.data.data.authorize;
        saveAuthToken(authorize);
        return true;
    }else{
        console.log('獲取token失敗');
        return false;
    }
}

//寫入資訊
function saveAuthToken (authorize) {
    //寫入狀態管理
    Taro.$store.dispatch(insertToken(authorize));
    //寫入快取
    Taro.setStorageSync('authorize',authorize)
}
複製程式碼

上面總共定義不少方法,我都有註釋,相信大家也都能看懂,其主要思想就是,如果使用者首次進入小程式,通過login進行登入,並將令牌存入本地,後面通過getToken方法進行獲取請求的令牌,以後使用者在進入小程式的時候,就進行授權資訊和登入狀態檢測,如果登入過期或者令牌過期則通過getAuthToken方法進行請求,獲取最新的身份令牌!

上面之所以要觸發一次 changeAppOnLaunch 是因為使用者每次進入需要進行一次checkSeesion檢測,如果使用者本次session_key未過期,一般來說本次小程式生命週期內就就不需要在進行checkSession檢測了,因此我們用一個狀態來標識使用者是否是重新進入小程式~

step3

假定我們的頁面的初始化請求都寫在componentDidMount生命週期內,那麼現在我們要做的就是要在頁面介面請求之前去判斷是否已經獲取到token,如果有token則進行正常的介面請求,否則就通過上面的登入獲取token,不然就會導致介面未攜帶令牌而發生錯誤~

那麼問題來了,我們如何阻塞componentDidMount而保證每次介面請求之前都已經拿到了token呢,這裡我的做法就是對Component進行二次封裝,採用的是ES7提供的裝飾器,關於裝飾器的使用,可以瞄一眼這篇文章~

通過裝飾器,我們可以將Component進行二次封裝,可以根據業務情況將授權、分享等全部進行歸一化處理,這裡我們建立一個pageInit.js,來實現這個功能~

//pageInit.js

import Taro from '@tarojs/taro'
import auth from '../utils/auth'
function pageInit() {
    return function Component(Component) {
        return class extends Component {

        constructor (props){
            super(props);
        }
        
        //onLoad
        componentWillMount(){
            //初始分享資訊
            initShareMenu(this.state);
        }
        
        //阻塞 didMount , 鑑權
        async componentDidMount() {
            let result = await auth.appCheckAuth();
            //授權成功
            if( result ){
            	//呼叫父元件的函式
            	super.componentDidMount && super.componentDidMount();
            }else{
            	//授權失敗
            	Taro.showToast({
                    title : '授權失敗' ,
                    icon : 'none' ,
                    mask : true
            	})
            }
        }
        
        //重寫分享
        onShareAppMessage(){
            let shareOptions = super.onShareAppMessage();
            //如果當前頁面配置分享使用配置的
            if( shareOptions ) return shareOptions;
            //預設分享
            return {
            	title : '預設分享內容'
            }
        }

        //重新下拉重新整理
        onPullDownRefresh(){
            if(super.onPullDownRefresh){
            	super.onPullDownRefresh();
            	setTimeout(() => {
            		Taro.stopPullDownRefresh();
            	}, 1500)
            }
        }
    }
};
}

/**
 * 初始化分享資訊
 */
function initShareMenu (state) {
    // 初始化頁面分享資訊
    if ( state && state.canShare ) {
    	Taro.showShareMenu({
            withShareTicket: false
    	})
    } else {
    	Taro.hideShareMenu();
    }
}

export default pageInit;

複製程式碼

核心思想是,通過asyncawait 阻塞,讓其先執行自身的 componentDidMount 方法,拿到授權資訊後,在呼叫父類的 componentDidMount 方法,這樣就可以保證授權資訊獲取到再進行介面請求了~

我們要做的就是在頁面內通過 @pageInit() 進行裝飾即可~

這裡我做了個測試,在清空全部授權資訊後,在頁面進行一次介面請求,可以看到先執行的是 lite 獲取使用者令牌,然後進行頁面的介面請求,保證了執行順序不會錯亂~

基於Taro框架的微信小程式JWT授權登入方案

小結

ok,到此為止,我們基本上已經算是完成了整套體系,接下來就是在介面請求中加上token就好了,這裡我就補不貼程式碼了~ 完成的程式碼可去倉庫自行檢視~

Taro對於我來說,也算是屬於初探,這篇文章也是希望能給跟我一樣萌新的小夥伴一個拋磚引玉的作用,如果有哪裡寫的不對的地方,還請批評斧正~

相關文章