微信小程序模擬 cookie

開發背景

現有系統已經有一套完整的接口,用戶狀態、驗證都是基於 cookie 的。javascript

部分業務要上小程序版本,衆所周知,微信小程序不支持 cookie 的。要上線的業務,最好的方式仍是基於現有這套接口作,改動不大,也最快。java

模擬 cookie

經過瀏覽器的開發工具,Network 欄查看請求,瀏覽器中的 cookie 會攜帶在每一個 httpRequest Headers 裏面,用 Cookie 做爲鍵名。小程序

那麼,在微信官方請求方式 wx.request 中,咱們設置 header,添加一個 Cookie 應該能夠得以模擬。微信小程序

問題又來了,怎麼獲取到服務器返回的 cookie 呢。api

經過登陸接口(登陸的時候,服務器端會植入 cookie 做爲 session),查看 http 返回頭。瀏覽器

wx.request({
    url: '/api/login',
    success: (data) => {
        if(data.statusCode === 200) {
            console.log(data);
            // data 中應該會有 Set-Cookie 或 set-cookie 的字樣,嗯,那就是服務器種下的 cookie
        }
    }
})

拿到 cookie 存入本地中,下次請求數據的時候直接塞進去,完美。緩存

格式化 cookie

本來覺得 cookie 只須要一進一出就能夠完美模擬,實際操做才發現,攜帶上去的 cookie 服務器沒法識別。安全

服務器返回的 cookie 中,會攜帶上不少儲存用的字段,例如 path=/;服務器

// 服務器放回的 cookie
let cookie = 'userKey=1234567890; Path=/; Expires=Thu, 21 Jun 2018 13:15:08 GMT; HttpOnly,userId=111; Path=/; Expires=Thu, 21 Jun 2018 13:15:08 GMT,nickName=; Path=/; Expires=Thu, 21 Jun 2018 13:15:08 GMT,userName=111111; Path=/; Expires=Thu, 21 Jun 2018 13:15:08 GMT,imgUrl=; Path=/; Expires=Thu, 21 Jun 2018 13:15:08 GMT';

// 模擬的是須要的格式樣式
let virtualCookie = 'userKey=1234567890; userName=111111; userId=111;';

媽耶~要怎麼過濾呢。微信

簡單粗糙的寫了一個過濾方案。

// cookie 的本地存儲位置
const COOKIE_KEY = '__cookie_key__';

/**
 * 格式化用戶須要的 cookie
 */
const normalizeUserCookie = (cookies = '') => {
    let __cookies = [];
    (cookies.match(/([\w\-.]*)=([^\s=]+);/g) || []).forEach((str) => {
        if (str !== 'Path=/;' && str.indexOf('csrfToken=') !== 0) {
            __cookies.push(str);
        }
    });
    wx.setStorageSync(COOKIE_KEY, __cookies.join(' '));
};

csrfToken 是接下來配合 Egg.js 用的,Path=/; 在某些應用下會是 path=/;

normalizeUserCookie 主要是過濾了 xx=xxx; 這樣的數據,而後排除 path=/; 這樣無心義的數據。

在登陸接口的時候,存上 cookie,在接下來的請求中帶上,那麼,應該、沒錯、可能、能夠模擬了。

配合 Egg.js

Egg 內置的 egg-security 插件默認對全部『非安全』的方法,例如 POST,PUT,DELETE 都進行 CSRF 校驗。

Egg.js 雖然能夠在配置中關閉 CSRF,可是,若是必定要使用呢?

首先,要弄明白一件事,csrfToken 怎麼來的。

通過屢次驗證得知,當 http 請求時,在約定位置沒有攜帶上 csrfToken 值,這次請求會在返回的 cookie 中攜帶上一個新的 csrfToken;當本次請求已攜帶上值,就不會產生成 csrfToken。當約定位置帶上的 csrfTokencookie 裏面的 csrfToken 一致時,經過驗證。

接上面的 格式化用戶須要的 cookie 操做,先拋開 csrfToken 單獨處理用戶狀態等。

在每次請求結束後,試着單獨拿 cookie 中可能存在的 csrfToken,有值就緩存,沒值跳過用舊值。

封裝一個 Ajax

本次小程序是基於 wepy 的,因此使用了優化後的 wepy.request;

基於 Egg.js 的版本。

可能與實際開發有點出入,適當修改。

import wepy from 'wepy';

export const HTTP_HOST = 'http://127.0.0.1:3000';

export const HTTP_HOST_API = `${HTTP_HOST}/api/wxmp`;

// cookie 的本地存儲位置
const COOKIE_KEY = '__cookie_key__';
// csrfToken 的本地存儲位置
const CSRF_TOKEN_KEY = '__csrf_token__';

/**
 * 清除用戶Cookie
 */
export const cleanUserCookie = () => {
    wx.setStorageSync(COOKIE_KEY, '');
}

/**
 * 格式化用戶須要的 cookie
 * @param {String} cookies
 */
export const normalizeUserCookie = (cookies = '') => {
    let __cookies = [];
    (cookies.match(/([\w\-.]*)=([^\s=]+);/g) || []).forEach((str) => {
        if (str !== 'path=/;' && str.indexOf('csrfToken=') !== 0) {
            __cookies.push(str);
        }
    });
    wx.setStorageSync(COOKIE_KEY, __cookies);
};

/**
 * 格式化 token
 */
const normalizeCsrfToken = () => {
    let __value = wx.getStorageSync(CSRF_TOKEN_KEY) || '';
    let __inputs = __value.match(/csrfToken=[\S]*/) || [];
    let __key = __inputs[0]; // csrfToken=1212132323;
    if (!!!__key) {
        return '';
    }
    // 脫水
    return __key.replace(/;$/, '').replace(/^csrfToken=/, '');
};

/**
 * 保存 csrf 的cookie
 * 不必定每次請求都會更新 cookie
 * @param {String} cookie
 */
const seveCsrfTokenCookie = (cookie) => {
    if (cookie) {
        wx.setStorageSync(CSRF_TOKEN_KEY, cookie);
    }
};

/**
 * 請求數據
 * @param {Object} opt
 */
export const doAjax = (opt) => {
    return new Promise((resolve, reject) => {
        let Cookies = wx.getStorageSync(COOKIE_KEY) || [];
        let csrf = normalizeCsrfToken();
        let url = opt.url;
        // 整理 Cookie
        Cookies.push(`csrfToken=${csrf};`);

        // 設置請求頭部
        opt.header = Object.assign(
            {
                'x-csrf-token': csrf,
                Cookie: Cookies.join(' ')
            },
            opt.header || {}
        );
        opt.success = (data) => {
            seveCsrfTokenCookie(data.header['set-cookie']);
            // 統一操做
            if (data.statusCode == 200) {
                if (url === '/login') {
                    normalizeUserCookie(data.header['set-cookie']);
                }
                resolve(data.data);
            } else {
                reject('未知錯誤,請重試一次');
            }
        };
        opt.fail = (err) => {
            reject(err);
        };
        opt.url = `${HTTP_HOST_API}${opt.url}`;
        wepy.request(opt);
    });
};

原文閱讀:微信小程序模擬 cookie

相關文章
相關標籤/搜索