基于Taro框架的微信小程序JWT授權(quán)登錄方案

背景

小程序這兩年可謂是風(fēng)風(fēng)火火伶丐,已經(jīng)涉及到了行業(yè)的方方面面,作為一枚前端碼農(nóng)疯特,為了緊跟時(shí)代的步伐哗魂,滿足市場(chǎng)的需要,樓主也步入了小程序的坑中漓雅,一探究竟录别,從最初的原生開發(fā)朽色,過渡到WePY的引入,又轉(zhuǎn)到了mpvue的框架组题,最近又準(zhǔn)備踩踩Taro的坑 了~

之所以從mpvue框架遷移到Taro框架葫男,一是由于該框架是京東凹凸實(shí)驗(yàn)室開發(fā)維護(hù)的,目前來看比較活躍崔列,更新迭代速度比較快梢褐,質(zhì)量上有一定的保障;二是該框架支持多端統(tǒng)一赵讯,一勞永逸盈咳,為后面拓展H5等其他業(yè)務(wù)奠定基礎(chǔ)~

因業(yè)務(wù)需求,我們采用JWT進(jìn)行身份鑒權(quán)边翼,所以才有了這篇JWT授權(quán)登錄 解(cai)決(keng)方(zhi)案(nan)鱼响,如果對(duì)JWT還不是很了解的小伙伴,請(qǐng)猛戳這里~

ok讯私,不廢話了热押,直接進(jìn)入你們最想看到代碼環(huán)節(jié)~

正文

關(guān)于Taro的安裝的使用西傀,我這里就不再贅述了斤寇,具體的步驟可以參考官網(wǎng)給出的教程,我們直接來看看授權(quán)登錄這一塊~

FBI WARNING : 本文相關(guān)的代碼是跟樓主目前公司的有些業(yè)務(wù)場(chǎng)景有關(guān)拥褂,如有不同娘锁,請(qǐng)自行更改即可~

初始化Taro項(xiàng)目時(shí),我們需要選擇使用Redux模版饺鹃,進(jìn)行狀態(tài)管理莫秆,然后默默等待依賴安裝完成,通過npm run dev:weapp命令就可以啟動(dòng)我們的小程序服務(wù)了~

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

step1

首先悔详,我們需要使用到Redux定義一些app相關(guān)的數(shù)據(jù)镊屎,方便我們后面的判斷和使用~

Redux三步走,reducer茄螃、constant缝驳、action 我這里就直接貼代碼了~

  • constants目錄
//constants  app.js

//更改app登錄狀態(tài)
export const CHANGE_APP_ON_LAUNCH = 'change_app_on_launch';

//寫入授權(quán)信息
export const INSERT_AUTHORIZE = 'insert_authorize';

  • actions目錄
// actions app.js

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

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

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

  • recuders目錄
import * as constants from '../constants/app'

const INITIAL_STATE ={
    //請(qǐng)求接口域名地址
    baseURL : 'https://xx.xxx.xx/' ,
    //應(yīng)用首次加載
    appOnLaunch : true ,
    //請(qǐng)求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 對(duì)象上归苍,這樣我們也就可以在后面通過Taro.$store這種方式直接獲取進(jìn)行操作處理用狱,即:

 //app.js
 
 componentDidMount() {
    //將redux狀態(tài)掛載到 Taro 對(duì)象上,方便使用
    Taro.$store = store;
}

step2

我們?cè)?code>src創(chuàng)建一個(gè)utils文件夾拼弃,用來存放我們一些公用的方法之類的文件~

由于微信小程序的限制夏伊,我們無法得知用戶的登錄狀態(tài)是否過期,所以我們必須要通過wx.checkSession 來接口檢測(cè)當(dāng)前用戶登錄態(tài)是否有效吻氧,做對(duì)應(yīng)的邏輯處理~

因此溺忧,我們創(chuàng)建一個(gè)auth.js文件咏连,用來微信登錄,獲取獲取授權(quán)信息鲁森,其流程可以參考官方的這張時(shí)序圖捻勉!

我們的JWT就是上面圖所提到的自定義登錄狀態(tài),它一般是長這樣的

其中exp是令牌過期時(shí)間刀森,iat令牌簽發(fā)時(shí)間踱启,nbf令牌生效時(shí)間,token是用戶狀態(tài)研底,包含用戶的身份信息埠偿,我們主要會(huì)用到exptoken 兩個(gè)字段,用來判斷令牌是否過期和發(fā)送請(qǐng)求攜帶用戶身份~

因此榜晦,我的做法是冠蒋,在用戶進(jìn)入小程序的時(shí)候通過Taro.checkSession(),檢查session_key是否過期乾胶,如果過期需要通過 Taro.login()重新登錄進(jìn)行授權(quán)抖剿,獲取JWT授權(quán)信息,否則就判斷用戶的令牌是否過期识窿,如果過期就要重新刷新令牌斩郎,重新獲取,其代碼如下~

// auth.js

import Taro from '@tarojs/taro'
import { insertToken , changeAppOnLaunch } from '../actions/app'
//獲取數(shù)據(jù)
export default class Auth {
//app授權(quán)
static appCheckAuth(){
    return new Promise(function (resolve) {
        const state = Taro.$store.getState();
        //如果有授權(quán)信息
        if( Auth.checkAuth() && !state.app.appOnLaunch ){
            //直接返回
            resolve(true);
        }else{
            //判斷session_key是否過期
            Taro.checkSession().then(async ()=>{
                //未過期檢查token是否有效
                if( !Auth.checkAuth() ){
                    //判斷是否 token 請(qǐng)求成功
                    let flag = await getAuthToken();
                    if( flag ) {
                        //更新app狀態(tài)
                        Taro.$store.dispatch(changeAppOnLaunch());
                        resolve(true);
                    }else{
                      //提示
                        Taro.showToast({
                        title : '獲取授權(quán)信息失敗' ,
                        icon : 'none' ,
                        mask : true
                        })
                    }
                }else{
                    //更新app狀態(tài)
                    Taro.$store.dispatch(changeAppOnLaunch());
                    //token 沒有過期喻频,直接返回
                    resolve(true);
                }
            }).catch(async (err)=> {
                console.log(err);
                let flag = await getAuthToken();
                //判斷是否 token 請(qǐng)求成功
                if( flag ) {
                    //更新app狀態(tài)
                    Taro.$store.dispatch(changeAppOnLaunch());
                    resolve(true);
                }else{
                    //提示
                    Taro.showToast({
                        title : '獲取授權(quán)信息失敗' ,
                        icon : 'none' ,
                        mask : true
                    })
                }
            })
        }
    })
}
    
    // 檢查令牌是否有效 true--> 有效  false--> 無效
    static checkAuth(){
        const state = Taro.$store.getState();
        //從緩存讀取授權(quán)信息
        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;
    }
}

//授權(quán)用戶 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) {
    //寫入狀態(tài)管理
    Taro.$store.dispatch(insertToken(authorize));
    //寫入緩存
    Taro.setStorageSync('authorize',authorize)
}

上面總共定義不少方法缩宜,我都有注釋,相信大家也都能看懂甥温,其主要思想就是锻煌,如果用戶首次進(jìn)入小程序,通過login進(jìn)行登錄姻蚓,并將令牌存入本地宋梧,后面通過getToken方法進(jìn)行獲取請(qǐng)求的令牌,以后用戶在進(jìn)入小程序的時(shí)候狰挡,就進(jìn)行授權(quán)信息和登錄狀態(tài)檢測(cè)捂龄,如果登錄過期或者令牌過期則通過getAuthToken方法進(jìn)行請(qǐng)求,獲取最新的身份令牌圆兵!

上面之所以要觸發(fā)一次 changeAppOnLaunch 是因?yàn)橛脩裘看芜M(jìn)入需要進(jìn)行一次checkSeesion檢測(cè)跺讯,如果用戶本次session_key未過期,一般來說本次小程序生命周期內(nèi)就就不需要在進(jìn)行checkSession檢測(cè)了殉农,因此我們用一個(gè)狀態(tài)來標(biāo)識(shí)用戶是否是重新進(jìn)入小程序~

step3

假定我們的頁面的初始化請(qǐng)求都寫在componentDidMount生命周期內(nèi)刀脏,那么現(xiàn)在我們要做的就是要在頁面接口請(qǐng)求之前去判斷是否已經(jīng)獲取到token,如果有token則進(jìn)行正常的接口請(qǐng)求超凳,否則就通過上面的登錄獲取token愈污,不然就會(huì)導(dǎo)致接口未攜帶令牌而發(fā)生錯(cuò)誤~

那么問題來了耀态,我們?nèi)绾巫枞?code>componentDidMount而保證每次接口請(qǐng)求之前都已經(jīng)拿到了token呢,這里我的做法就是對(duì)Component進(jìn)行二次封裝暂雹,采用的是ES7提供的裝飾器首装,關(guān)于裝飾器的使用,可以瞄一眼這篇文章~

通過裝飾器杭跪,我們可以將Component進(jìn)行二次封裝仙逻,可以根據(jù)業(yè)務(wù)情況將授權(quán)、分享等全部進(jìn)行歸一化處理涧尿,這里我們創(chuàng)建一個(gè)pageInit.js系奉,來實(shí)現(xiàn)這個(gè)功能~

//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 , 鑒權(quán)
        async componentDidMount() {
            let result = await auth.appCheckAuth();
            //授權(quán)成功
            if( result ){
                //調(diào)用父組件的函數(shù)
                super.componentDidMount && super.componentDidMount();
            }else{
                //授權(quán)失敗
                Taro.showToast({
                    title : '授權(quán)失敗' ,
                    icon : 'none' ,
                    mask : true
                })
            }
        }
        
        //重寫分享
        onShareAppMessage(){
            let shareOptions = super.onShareAppMessage();
            //如果當(dāng)前頁面配置分享使用配置的
            if( shareOptions ) return shareOptions;
            //默認(rèn)分享
            return {
                title : '默認(rèn)分享內(nèi)容'
            }
        }

        //重新下拉刷新
        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 阻塞缺亮,讓其先執(zhí)行自身的 componentDidMount 方法,拿到授權(quán)信息后桥言,在調(diào)用父類的 componentDidMount 方法萌踱,這樣就可以保證授權(quán)信息獲取到再進(jìn)行接口請(qǐng)求了~

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

這里我做了個(gè)測(cè)試,在清空全部授權(quán)信息后号阿,在頁面進(jìn)行一次接口請(qǐng)求并鸵,可以看到先執(zhí)行的是 lite 獲取用戶令牌,然后進(jìn)行頁面的接口請(qǐng)求倦西,保證了執(zhí)行順序不會(huì)錯(cuò)亂~

小結(jié)

ok能真,到此為止,我們基本上已經(jīng)算是完成了整套體系扰柠,接下來就是在接口請(qǐng)求中加上token就好了,這里我就補(bǔ)不貼代碼了~
完成的代碼可去倉庫自行查看~

Taro對(duì)于我來說疼约,也算是屬于初探卤档,這篇文章也是希望能給跟我一樣萌新的小伙伴一個(gè)拋磚引玉的作用,如果有哪里寫的不對(duì)的地方程剥,還請(qǐng)批評(píng)斧正~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劝枣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子织鲸,更是在濱河造成了極大的恐慌舔腾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搂擦,死亡現(xiàn)場(chǎng)離奇詭異稳诚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瀑踢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門扳还,熙熙樓的掌柜王于貴愁眉苦臉地迎上來才避,“玉大人,你說我怎么就攤上這事氨距∩J牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵俏让,是天一觀的道長楞遏。 經(jīng)常有香客問我,道長首昔,這世上最難降的妖魔是什么橱健? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮沙廉,結(jié)果婚禮上拘荡,老公的妹妹穿的比我還像新娘。我一直安慰自己撬陵,他們只是感情好珊皿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巨税,像睡著了一般蟋定。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上草添,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天驶兜,我揣著相機(jī)與錄音,去河邊找鬼远寸。 笑死抄淑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驰后。 我是一名探鬼主播肆资,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼灶芝!你這毒婦竟也來了郑原?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤夜涕,失蹤者是張志新(化名)和其女友劉穎犯犁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體女器,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酸役,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片簇捍。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡只壳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暑塑,到底是詐尸還是另有隱情吼句,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布事格,位于F島的核電站惕艳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驹愚。R本人自食惡果不足惜远搪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逢捺。 院中可真熱鬧谁鳍,春花似錦、人聲如沸劫瞳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽志于。三九已至涮因,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伺绽,已是汗流浹背养泡。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奈应,地道東北人澜掩。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像钥组,于是被迫代替她去往敵國和親输硝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容