現有系統已經有一套完整的接口,用戶狀態、驗證都是基於 cookie
的。javascript
部分業務要上小程序版本,衆所周知,微信小程序不支持 cookie
的。要上線的業務,最好的方式仍是基於現有這套接口作,改動不大,也最快。java
經過瀏覽器的開發工具,Network
欄查看請求,瀏覽器中的 cookie
會攜帶在每一個 http
的 Request 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
中,會攜帶上不少儲存用的字段,例如 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
內置的egg-security
插件默認對全部『非安全』的方法,例如 POST,PUT,DELETE 都進行CSRF
校驗。
Egg.js
雖然能夠在配置中關閉 CSRF
,可是,若是必定要使用呢?
首先,要弄明白一件事,csrfToken
怎麼來的。
通過屢次驗證得知,當 http
請求時,在約定位置沒有攜帶上 csrfToken
值,這次請求會在返回的 cookie
中攜帶上一個新的 csrfToken
;當本次請求已攜帶上值,就不會產生成 csrfToken
。當約定位置帶上的 csrfToken
與 cookie
裏面的 csrfToken
一致時,經過驗證。
接上面的 格式化用戶須要的 cookie
操做,先拋開 csrfToken
單獨處理用戶狀態等。
在每次請求結束後,試着單獨拿 cookie
中可能存在的 csrfToken
,有值就緩存,沒值跳過用舊值。
本次小程序是基於 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