京东购物小程序cookie方案实践(附Demo)

栏目: IT技术 · 发布时间: 4年前

内容简介:如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

一、前言

早期为了解决“会话保持”的需求,社区中出现了「cookie 方案」并最终成为 W3C 标准:当某个网站登录成功后,客户端(浏览器)收到一个 cookie 标识(文本)并保存下来,在后续请求中会自动带上这个字段,由此 Web 后台可以判断是否同一个用户,从而使“会话”得以延续。

微信小程序没有像浏览器一样内置实现了 cookie 方案,需要开发者自行模拟,而原先京东购物小程序及京喜小程序(现微信一级购物入口)是从微信及手 Q 购物入口 H5 中迁移迭代出来的,也就是说我们不仅要在小程序中模拟一套 cookie 方案,并且要保持和原业务对 cookie 处理逻辑的一致,为此我们将实现方向确定为“基于小程序开放能力,和浏览器保持一致”。

微信小程序开放了 数据缓存 Storage [1]网络 Network [2] 这两种能力,通过这两套 API,我们可以自行 DIY 一个 cookie 方案。

PS:本文所有代码及使用示例都可以 在这里 [3] 找到,阅读本文时配合实践,效果更佳。

二、浏览器中的 cookie

为了保持后端对 cookie 的处理逻辑和原来的 H5 一致,小程序的实现需要往浏览器看齐。

所以模拟小程序的 cookie 前,先看看浏览器的 cookie 机制,主要有以下几个部分:

  • 本地存储:浏览器会在本地分配一块空间,存储 cookie

  • 请求携带:每次发起请求,都会从本地取出 cookie 并追加在请求头上

  • 响应设置:当响应头有 Set-Cookie 字段时,需要解析并更新

  • 过期时间:每个 cookie 字段有单独的过期时间,并且到期会自动清除

  • 读写操作:暴露 API 给前端 JS 调用,可进行增删改查操作

  • 作用域:路径 path、域名 domin

  • 编码:cookie 值,在网络传输需要 encode,建议存储也一样

  • 其它:HttpOnly、Secure、SameSite

在浏览器的 DevTools 中,可以看到当前站点下的 Cookie 明细: 京东购物小程序cookie方案实践(附Demo)

三、小程序中的 cookie 实现

方案设计

在小程序中模拟 Cookie,主要涉及五个部分: 京东购物小程序cookie方案实践(附Demo) 其中我们会重点关注 「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方案实践(附Demo)

读写操作

这部分主要作为“公共基础库“的角色,为外部业务提供增删改查 cookie 的 API。

1. 获取 cookie———— 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) : ''
}

2. 设置 cookie———— setCookie()

步骤:从 Storage 中取出完整 cookies ==> 解析入参 ==> 覆盖更新 ==> 同步到本地 Storage

首先看下本 API 设计需求:

  • 设置单个/多个 cookie

  • 直接传值/传 cookieItem(Object)

  • 时间格式 maxAge/expires

调用示例如下:

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))
}

3. 删除 cookie———— removeCookie()

步骤:从 Storage 中取出完整 cookies ==> 删除指定的 cookie 项 ==> 同步到本地 Storage

function removeCookie(cookieName) {
    let cookies = wx.getStorageSync('cookies') // try/catch 略过

    delete cookies[cookieName]

    saveCookiesToStorage(Object.assign({}, cookies))
}

四、Cookie 在网络中的传递

本节主要简单实现设计图中的【Request 基础库】部分

京东购物小程序cookie方案实践(附Demo)

如上图所示,Cookie 在网络中的传输主要有四个过程:

  • 客户端发起 HTTP 请求

  • 服务端响应,并在响应头加上 Set-Cookie ,客户端接受并解析保存
  • 下一次客户端发起 HTTP 请求,在请求头加上 Cookie
  • 服务端识别出请求头的 Cookie ,作出相应处理

以下是对一个请求的抓包示例: 京东购物小程序cookie方案实践(附Demo)

在小程序中,请求发起有两种方式: HTTPWebSocket ,这里以 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 点:

1. 请求携带

步骤:(每次发请求前)从 Storage 中取出完整 cookies ==> 转化为 HTTP 规范的请求头 Cookie 格式 ==> 设置到 Request Header

上面代码中的 getCookiesStr() 直接取 cookies 拼接即可,返回示例: cookie1=xxx;cookie2=yyy

2. 响应设置

步骤:(每次收到响应后)解析 Response HeaderSet-Cookie 字段 ==> 转为标准 Cookie 格式 ==> setCookie()

这里处理 Set-Cookie 内容时,有几个点需要留意:- 最基本的格式: Set-Cookie: <cookie-name>=<cookie-value> - 可能同时包含多个 cookie 字段,以,分割(但需要排除时间值里的,) - 时间格式:Max-Age/Expires (不区分大小写)

具体实现可在文末 Demo 中找到。

3. 编码问题

「Cookie 值编码方式」是容易产生困惑的地方,目前看到的广泛做法都是使用「URL 编码」。

但笔者翻阅 RFC6265 [4] 发现,原始规范中并没有对编码进行指定,比如在第四章 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 层」。

1. 初始化

首先需要在内存中声明一个 _COOKIES (命名自行 diy),建议在 cookie 基础库中声明,便于统一维护。

2. 读

前面初始化时已经从 Storage 读取一次 cookies,后续 getCookie 就直接读内存的 _COOKIES 即可。

3. 写

写操作直接更新内存,间接更新 Storage。如果有高频写场景,可以考虑做个任务队列进行节流。

六、单元测试

微信官方在 2019 年 5 月推出了「小程序自动化 SDK」 miniprogram-automator ,经过半年多的迭代,目前已基本稳定下来。

在购物小程序场景试用了一下,cookie 相关的用例很快就完成了,简直是开发者的福音:真香!!!

京东购物小程序cookie方案实践(附Demo)

实际项目中,对 cookie 的单元测试可以分为两类:

  • 小程序全局范围的 cookie 验证(比如初始化小程序后,有没有种下版本号、访问行为等关键 cookie)

  • cookie 基础库 API 验证(比如 get/set/remove 等各个 API 是否正常工作)

以验证 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 安全

Cookie 安全是一个比较大的话题,这里只简单列出和小程序相关的几个点。

path、domin、HttpOnly、Secure、SameSite

小程序中已经做了一些安全措施,比如只能走 HTTPS、合法域名需要管理员到微信后台进行配置、Storage 只能由写入它的小程序中访问,等等。因此 path、domin、HttpOnly、Secure、SameSite 这些字段在小程序环境下的价值没有浏览器环境大,本例中没有使用(懒..),而实际业务场景可以按自身情况决定是否要使用。

白名单机制

1 前端维护(大小/数量)

通常浏览器保持的 Cookie 数据不超过 4k,部分浏览器限制同一站点最多 cookie 数为 20 个。

如果业务庞大的话,建议在 Cookie 基础库做一套「白名单」机制,在白名单内才可以写入,以此防止“非法写入”或“内容超大导致信息丢失”的问题。

2 后台维护(网关白名单)

同样的,建议从网关层面,建立一个“可信 cookie”白名单,自动过滤请求中的“非法 cookie”字段。

前端防篡改

小程序前端更多是防“误改”————即在操作 Cookie 过程中,发生了意料之外的修改。通常发生在 JS “引用拷贝”特性上,比如前面提到的内存维护一个 _Cookies ,如果有一个 API getAllCookies() 直接将这份内存版 cookies 暴露出去,对象引用容易被连带修改。所以 cookie 基础库需要控制暴露 API 的能力范围,并对取值进行“深拷贝”。

Session

Session 机制将用户状态放在了服务端维护,具备更好的安全性,而且目前各种后端对于 session 的存储和同步都有很成熟的技术方案,有条件的业务应以 Session 为主做会话保持。

指纹上报

用户访问时生成设备指纹并上报(通常是登录/结算等环节),业务后台配合风控系统,遇到异常请求时下发验证环节。

八、完整小程序实现 Demo

代码片段:https://developers.weixin.qq.com/s/x4sFASmh7xdq

京东购物小程序cookie方案实践(附Demo)

九、小结

本文先解析了浏览器的 Cookie 机制 运作原理,然后使用「数据缓存」和「网络」能力,以 公共基础库 的形式,在小程序中实现了一套 Cookie 方案。希望对大家有所帮助。

十、相关链接

  • RFC6265(HTTP 状态管理机制规范) [5]

  • HTTP Cookies Explained [6]

  • 小程序数据缓存 Storage API [7]

  • 小程序网络 Network API [8]

  • 小程序自动化 SDK [9]

  • 小程序实现 Demo [10]

参考资料

[1]

数据缓存 Storage: https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html

[2]

网络 Network: https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html

[3]

在这里: https://developers.weixin.qq.com/s/x4sFASmh7xdq

[4]

RFC6265: https://tools.ietf.org/html/rfc6265

[5]

RFC6265(HTTP 状态管理机制规范): https://tools.ietf.org/html/rfc6265

[6]

HTTP Cookies Explained: https://humanwhocodes.com/blog/2009/05/05/http-cookies-explained/

[7]

小程序数据缓存 Storage API: https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html

[8]

小程序网络 Network API: https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html

[9]

小程序自动化 SDK: https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/

[10]

小程序实现 Demo: https://developers.weixin.qq.com/s/x4sFASmh7xdq

:heart:看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点赞,让更多的人也能看到介绍内容(收藏不点赞,都是耍流氓-_-)

  2. 关注公众号“前端劝退师”,不定期分享原创知识。

  3. 也看看其他文章

京东购物小程序cookie方案实践(附Demo)

也可以来我的 GitHub 博客里拿所有文章的源文件:

前端劝退指南 :https://github.com/roger-hiro/BlogFN


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming in Haskell

Programming in Haskell

Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99

Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具