微信小程序模拟cookie的实现
开发背景
现有系统已经有一套完整的接口,用户状态、验证都是基于 cookie 的。
部分业务要上小程序版本,众所周知,微信小程序不支持 cookie 的。要上线的业务,最好的方式还是基于现有这套接口做,改动不大,也最快。
模拟 cookie
通过浏览器的开发工具,network 栏查看请求,浏览器中的 cookie 会携带在每个 http 的 request headers 里面,用 cookie 作为键名。
那么,在微信官方请求方式 wx.request 中,我们设置 header,添加一个 cookie 应该可以得以模拟。
问题又来了,怎么获取到服务器返回的 cookie 呢。
通过登录接口(登录的时候,服务器端会植入 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。当约定位置带上的 csrftoken 与 cookie 里面的 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); }); };
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。