阅读 190

Koa 依赖的库 cookies(koa使用)

在使用 koa 时,我们可以使用 context 下面的 cookies 来进行 cookie 管理,在源码可以看到,context 下面的 cookies 实际上就是一个 Cookies 的实例,它是由 cookies 库提供的。

我们操作 cookie 最常用的方法是 get 和 set,服务器可以在 response header 中通过 Set-Cookie 来给浏览器添加 cookie,浏览器收到 Set-Cookie header 后会将 cookie 内容保存,当用户使用浏览器再次请求同域资源时,浏览器会自动在 request header 中添加 cookie 信息。因此 cookies 库中的 get 和 set 的实现原理就是控制这两个 header。

常规 cookie get 和 set 逻辑并不复杂,和其他 header 的处理方式一样,get 时使用正则表达式匹配 cookie header,在 set 时把参数处理成字符串设置到 Set-Cookie header 上,由于 Cookie 的结构比较复杂,在 cookies 内部定义了一个 Cookie 类型来封装 Cookie 格式数据:

function Cookie(name, value, attrs) {   if (!fieldContentRegExp.test(name)) {     throw new TypeError('argument name is invalid');   }   if (value && !fieldContentRegExp.test(value)) {     throw new TypeError('argument value is invalid');   }   this.name = name   this.value = value || ""   for (var name in attrs) {     this[name] = attrs[name]   }   if (!this.value) {     this.expires = new Date(0)     this.maxAge = null   }   if (this.path && !fieldContentRegExp.test(this.path)) {     throw new TypeError('option path is invalid');   }   if (this.domain && !fieldContentRegExp.test(this.domain)) {     throw new TypeError('option domain is invalid');   }   if (this.sameSite && this.sameSite !== true && !SAME_SITE_REGEXP.test(this.sameSite)) {     throw new TypeError('option sameSite is invalid')   } } Cookie.prototype.path = "/"; Cookie.prototype.expires = undefined; Cookie.prototype.domain = undefined; Cookie.prototype.httpOnly = true; Cookie.prototype.sameSite = false; Cookie.prototype.secure = false; Cookie.prototype.overwrite = false; 复制代码

在上面我们可以看到设置 cookie 时我们可以添加的属性信息,这些内容会创建一个 Cookie 对象,最终通过 toHeader 方法来把对象转为字符串格式:

Cookie.prototype.toHeader = function() {   var header = this.toString()   if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge);   if (this.path     ) header += "; path=" + this.path   if (this.expires  ) header += "; expires=" + this.expires.toUTCString()   if (this.domain   ) header += "; domain=" + this.domain   if (this.sameSite ) header += "; samesite=" + (this.sameSite === true ? 'strict' : this.sameSite.toLowerCase())   if (this.secure   ) header += "; secure"   if (this.httpOnly ) header += "; httponly"   return header }; 复制代码

由于 cookie 常用于保存用户身份信息,因此对 cookie 有很高的安全要求,cookies 库提供了签名相关逻辑,这里是使用 keygrip 库实现的。在 get 方法中传入参数 { signed: true } 可以获取签名 cookie,签名 cookie 的 key 为原始名后面加 .sig 后缀,匹配签名 cookie 有几种情况:

  • 如果签名 cookie 哈希与第一个 key 匹配,则返回原始 cookie 值。

  • 如果签名 cookie 哈希与任何其他 key 匹配,则返回原始 cookie 值并将签名cookie 的值更新为第一个 key 的哈希。

  • 如果签名 cookie 哈希与任何 key 都不匹配,则不返回任何内容并删除 cookie。

Cookies.prototype.get = function(name, opts) {   var sigName = name + ".sig"     , header, match, value, remote, data, index     , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys   header = this.request.headers["cookie"]   if (!header) return   match = header.match(getPattern(name))   if (!match) return   value = match[1]   if (!opts || !signed) return value   remote = this.get(sigName)   if (!remote) return   data = name + "=" + value   if (!this.keys) throw new Error('.keys required for signed cookies');   index = this.keys.index(data, remote)   if (index < 0) {     this.set(sigName, null, {path: "/", signed: false })   } else {     index && this.set(sigName, this.keys.sign(data), { signed: false })     return value   } }; 复制代码

在 set 方法的 opts 参数中我们可以设置 cookie 的属性信息:maxAgeexpirespathdomainsecurehttpOnlysameSitesignedoverwrite 等,其中对于设置了 secure 为 true 的 cookie 需要检查是否为安全环境,非安全环境不可以发送:

req.protocol === 'https' || req.connection.encrypted 复制代码

对于设置 signed 为 true 的 cookie,在设置 cookie 前会添加签名,即 key 后面添加 .sig 后缀,值使用 keygrip 签名处理。

处理好的 cookie 会调用 pushCookie 方法,使用 toHeader 把 Cookie 对象转为字符串信息保存,如果 overwrite 为 true 会删除同名的 cookie。

最后调用 response 上的 setHeader 方法添加 Set-Cookie 来为浏览器设置 cookie。这里有一个应该是版本兼容的判断,如果 response 上面有 set 方法此时应该没有 setHeader 方法,这种情况调用的是 http.OutgoingMessage.prototype 的 setHeader 方法,OutgoingMessage 是 response 的父类,相关方法可以在 node 文档中查到。

var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader setHeader.call(res, 'Set-Cookie', headers) 复制代码

至此 cookies 库内部的处理就完成了,在实际开发中使用 cookie 很方便,但是有时也会带来限制和安全问题,因此实际使用时需要结合具体的场景。


作者:丨隋堤倦客丨
链接:https://juejin.cn/post/7031711750416416776


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐