做者:張志豪html
早期爲了解決「會話保持」的需求,社區中出現了「cookie方案」並最終成爲W3C標準:當某個網站登陸成功後,客戶端(瀏覽器)收到一個cookie標識(文本)並保存下來,在後續請求中會自動帶上這個字段,由此Web後臺能夠判斷是否同一個用戶,從而使「會話」得以延續。前端
微信小程序沒有像瀏覽器同樣內置實現了cookie方案,須要開發者自行模擬,而原先京東購物小程序及京喜小程序(現微信一級購物入口)是從微信及手Q購物H5中遷移迭代出來的,也就是說咱們不只要在小程序中模擬一套cookie方案,而且要保持和原業務對cookie處理邏輯的一致,爲此咱們將實現方向肯定爲「基於小程序開放能力,和瀏覽器保持一致」。node
微信小程序開放了 數據緩存 Storage 和 網絡 Network 這兩種能力,經過這兩套API,咱們能夠自行DIY一個cookie方案。小程序
PS:本文全部代碼及使用示例均可以 在這裏 找到,閱讀本文時配合實踐,效果更佳。後端
爲了保持後端對cookie的處理邏輯和原來的H5一致,小程序的實現須要往瀏覽器看齊。微信小程序
因此模擬小程序的cookie前,先看看瀏覽器的cookie機制,主要有如下幾個部分:api
在瀏覽器的DevTools
中,能夠看到當前站點下的Cookie明細: 瀏覽器
在小程序中模擬Cookie,主要涉及五個部分: 緩存
其中咱們會重點關注 「Cookie基礎庫」 的實現,另外也會給出「Request基礎庫」的封裝示例。小程序提供了 「數據緩存 Storage API」(能夠理解爲Web規範中的LocalStorage
),支持存儲「原生類型、Date、及可以經過JSON.stringify序列化的對象」。安全
咱們能夠利用這些API,在Storage中新開一個cookies
字段進行存儲:
// 存: wx.setStorageSync('cookies', cookies) // 取: wx.getStorageSync('cookies') 複製代碼
其中cookies
的「存儲結構」以下:
// cookies = { cookie1: { // 「最小cookie單元」 ==> cookieItem name: 'cookie1', // cookie名 value: 'xxx', // cookie值 expires: 'Fri, 17 Jan 2020 08:49:41 GMT' // 過時時間,使用GMT(格林威治標準時間)格式 } }, 複製代碼
上面的cookie1
即是一個「最小cookie單元cookieItem
」,包含了3個字段(name、value、expires),是本文中定義的「標準cookie格式」,也是cookie操做的基本單元。
打開【微信開發工具】的Storage
選項卡,能夠查看本地存儲的狀況:
這部分主要做爲「公共基礎庫「的角色,爲外部業務提供增刪改查cookie的API。
getCookie()
步驟:從Storage中取出完整cookies ==> 取出指定name的cookie項 ==> 校驗有效期 ==> 返回值value
實現以下:
function getCookie(name = '') { let cookies = wx.getStorageSync('cookies') // try/catch 略過 let { value, expires } = cookies[name] || {} return (name && expires && !isExpired(expires)) ? decodeURIComponent(cookieItem.value) : '' } 複製代碼
setCookie()
步驟:從Storage中取出完整cookies ==> 解析入參 ==> 覆蓋更新 ==> 同步到本地Storage
首先看下本API設計需求:
調用示例以下:
setCookie({ cookie1: 12345, cookie2: '12345' }) setCookie({ cookie1: { value: 12345, maxAge: 3600 * 24 // 自定義有效期(這裏示例是24小時) }, cookie2: { value: '12345', expires: 'Wed, 21 Oct 2015 07:28:00 GMT' // 標準GMT格式 } }) 複製代碼
這裏可對入參遍歷,而cookie子項不管直接傳值value仍是傳了詳細object,都儘可能的獲取name/value/expires/maxAge
,傳給格式化函數轉爲標準的cookieItem
:
function setCookie(cookiesParam) { let oldCookies = wx.getStorageSync('cookies') // try/catch 略過 let newCookies = {} // 由 cookiesParam 轉化爲標準格式後的cookies for (let name in cookiesParam) { if (isObject(cookiesParam[name])) { // 傳入是Object格式 let { value, expires, maxAge } = cookiesParam[name] // 轉換爲標準cookie格式(cookieItem) newCookies[name] = getStandardCookieItem({ name, value, expires, maxAge }) } else { newCookies[name] = getStandardCookieItem({ name, value: cookiesParam[name] }) } } // 同步到本地Storage saveCookiesToStorage(Object.assign({}, oldCookies, newCookies)) } 複製代碼
removeCookie()
步驟:從Storage中取出完整cookies ==> 刪除指定的cookie項 ==> 同步到本地Storage
function removeCookie(cookieName) { let cookies = wx.getStorageSync('cookies') // try/catch 略過 delete cookies[cookieName] saveCookiesToStorage(Object.assign({}, cookies)) } 複製代碼
本節主要簡單實現設計圖中的【Request基礎庫】部分
如上圖所示,Cookie在網絡中的傳輸主要有四個過程:
Set-Cookie
,客戶端接受並解析保存Cookie
Cookie
,做出相應處理如下是對一個請求的抓包示例:
在小程序中,請求發起有兩種方式:HTTP
和WebSocket
,這裏以HTTP爲例,先對請求api進行「封裝」:
function requestPro({ url, data, header, method = 'GET' }) { return new Promise((resolve, reject) => { wx.request({ url, data, header: Object.assign({}, { 'Cookie': CookieLib.getCookiesStr() }, header), // 請求頭————帶上Cookie success (res) { let { data : resData, header, statusCode } = res let setCookieStr = header['Set-Cookie'] || header['set-cookie'] || '' CookieLib.setCookieFromHeader(setCookieStr) // 響應頭————解析Set-Cookie resolve(resData) }, fail (err) { reject(err) } }) }) } 複製代碼
如上代碼所示,Cookie在前端側請求模塊中的處理主要有3點:
步驟:(每次發請求前)從Storage中取出完整cookies ==> 轉化爲HTTP規範的請求頭Cookie格式 ==> 設置到Request Header
中
上面代碼中的getCookiesStr()
直接取cookies拼接便可,返回示例:cookie1=xxx;cookie2=yyy
。
步驟:(每次收到響應後)解析Response Header
的Set-Cookie
字段 ==> 轉爲標準Cookie格式 ==> setCookie()
這裏處理Set-Cookie
內容時,有幾個點須要留意: - 最基本的格式:Set-Cookie: <cookie-name>=<cookie-value>
- 可能同時包含多個cookie字段,以,分割(但須要排除時間值裏的,) - 時間格式:Max-Age/Expires (不區分大小寫)
具體實現可在文末Demo中找到。
「Cookie值編碼方式」是容易產生困惑的地方,目前看到的普遍作法都是使用「URL編碼」。
但筆者翻閱 RFC6265 發現,原始規範中並無對編碼進行指定,好比在第四章 Server Requirements (服務端)中是這樣描述:
To maximize compatibility with user agents, servers that wish to store arbitrary data in a cookie-value SHOULD encode that data, for example, using Base64 [RFC4648].
「爲了最好的兼容效果,服務端應該對cookie值進行編碼,例如使用Base64。」
而在第五章 User Agent Requirements (客戶端,也就是瀏覽器),則是「建議以第四章服務端的實現爲準」。
總之規範並無指定使用「URL編碼」,但基於該編碼方案已經深刻人心,也就順其天然成了「默認選擇」。那這裏也不作例外,瀏覽器怎麼作,咋們小程序也保持一致。
在瀏覽器中,推薦cookie值通過encode
編碼後保存下來,因此直接取到的也是encode
後的值,因此追加在請求頭Cookie
字段,就不須要decode
解碼了,直接拼接便可(但基礎庫API的get操做最終須要進行decode
解碼)。
而對於響應頭Set-Cookie
的值,咱們認爲後端已經作了encode
編碼,因此前端不須要處理,直接存進 Storage 便可。
前面實現中每次讀寫cookie都會調用小程序Storage API(並且是同步的),小程序框架會讀寫到本地Storage。 對於高頻場景,能夠將cookie在內存中維護一份,讀寫都直接走「內存層」,有更新才同步到「Storage層」。
首先須要在內存中聲明一個_COOKIES
(命名自行diy),建議在cookie基礎庫中聲明,便於統一維護。
前面初始化時已經從Storage讀取一次cookies,後續getCookie就直接讀內存的_COOKIES
便可。
寫操做直接更新內存,間接更新Storage。 若是有高頻寫場景,能夠考慮作個任務隊列進行節流。
微信官方在2019年5月推出了「小程序自動化 SDK」 miniprogram-automator
,通過半年多的迭代,目前已基本穩定下來。
在購物小程序場景試用了一下,cookie相關的用例很快就完成了,簡直是開發者的福音:真香!!!
實際項目中,對cookie的單元測試能夠分爲兩類:
以驗證setCookie()
API爲例:
it('API驗證:setCookie()', async () => { await miniProgram.evaluate(() => { wx.CookieLib.setCookie({ // 調用API cookie1: 12345, }) }) let { cookies } = await miniProgram.callWxMethod('getStorageSync', 'cookies') expect(cookies['cookie1'].value).toBe(12345) // 指望成功設置cookie1爲12345 }) 複製代碼
這裏爲了方便測試用例調用基礎庫API,在小程序啓動前,把Cookie基礎庫(CookieLib)掛到了wx
對象上,實現方式是使用node讀寫文件的API去【植入代碼】:
fs.appendFileSync('./your_project/app.js', ''\n wx.CookieUtil = require(\'./lib/cookie.js\');\n'')
複製代碼
Cookie安全是一個比較大的話題,這裏只簡單列出和小程序相關的幾個點。
小程序中已經作了一些安全措施,好比只能走HTTPS、合法域名須要管理員到微信後臺進行配置、Storage只能由寫入它的小程序中訪問,等等。 所以path、domin、HttpOnly、Secure、SameSite
這些字段在小程序環境下的價值沒有瀏覽器環境大,本例中沒有使用(懶..),而實際業務場景能夠按自身狀況決定是否要使用。
前端維護(大小/數量) 一般瀏覽器保持的Cookie數據不超過4k,部分瀏覽器限制同一站點最多cookie數爲20個。 若是業務龐大的話,建議在Cookie基礎庫作一套「白名單」機制,在白名單內才能夠寫入,以此防止「非法寫入」或「內容超大致使信息丟失」的問題。
後臺維護(網關白名單) 一樣的,建議從網關層面,創建一個「可信cookie」白名單,自動過濾請求中的「非法cookie」字段。
小程序前端更可能是防「誤改」————即在操做Cookie過程當中,發生了意料以外的修改。一般發生在JS「引用拷貝」特性上,好比前面提到的內存維護一個_Cookies
,若是有一個APIgetAllCookies()
直接將這分內存版cookies暴露出去,對象引用容易被連帶修改。因此cookie基礎庫須要控制暴露API的能力範圍,並對取值進行「深拷貝」。
Session機制將用戶狀態放在了服務端維護,具有更好的安全性,並且目前各類後端對於session的存儲和同步都有很成熟的技術方案,有條件的業務應以Session爲主作會話保持。
用戶訪問時生成設備指紋並上報(一般是登陸/結算等環節),業務後臺配合風控系統,遇到異常請求時下發驗證環節。
代碼片斷:developers.weixin.qq.com/s/x4sFASmh7…
本文先解析了瀏覽器的 Cookie機制 運做原理,而後使用「數據緩存」和「網絡」能力,以 公共基礎庫 的形式,在小程序中實現了一套 Cookie方案。但願對你們有所幫助。
若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送: