文章导读

cookie-parser是Express的中间件,用来实现cookie的解析,是官方脚手架内置的中间件之一。

它的使用非常简单,但在使用过程中偶尔也会遇到问题。一般都是因为对Express + cookie-parser的签名、验证机制不了解导致的。

本文深入讲解Express + cookie-parser的签名和验证的实现机制,以及cookie签名是如何增强网站的安全性的。

文本同步收录于GitHub主题系列《Nodejs学习笔记》

入门例子:cookie设置与解析

先从最简单的例子来看下cookie-parser的使用,这里采用默认配置。

  1. cookie设置:使用Express的内置方法res.cookie()
  2. cookie解析:使用cookie-parser中间件。
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express(); app.use(cookieParser()); app.use(function (req, res, next) {
console.log(req.cookies.nick); // 第二次访问,输出chyingp
next();
}); app.use(function (req, res, next) {
res.cookie('nick', 'chyingp');
res.end('ok');
}); app.listen(3000);

在当前场景下,cookie-parser中间件大致实现如下:

app.use(function (req, res, next) {
req.cookies = cookie.parse(req.headers.cookie);
next();
});

进阶例子:cookie签名与解析

出于安全的考虑,我们通常需要对cookie进行签名。

例子改写如下,有几个注意点:

  1. cookieParser初始化时,传入secret作为签名的秘钥。
  2. 设置cookie时,将signed设置为true,表示对即将设置的cookie进行签名。
  3. 获取cookie时,可以通过req.cookies,也可以通过req.signedCookies获取。
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express(); // 初始化中间件,传入的第一个参数为singed secret
app.use(cookieParser('secret')); app.use(function (req, res, next) {
console.log(req.cookies.nick); // chyingp
console.log(req.signedCookies.nick); // chyingp
next();
}); app.use(function (req, res, next) {
// 传入第三个参数 {signed: true},表示要对cookie进行摘要计算
res.cookie('nick', 'chyingp', {signed: true});
res.end('ok');
}); app.listen(3000);

签名前的cookie值为chyingp,签名后的cookie值为s%3Achyingp.uVofnk6k%2B9mHQpdPlQeOfjM8B5oa6mppny9d%2BmG9rD0,decode后为s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0

下面就来分析下,cookie的签名、解析是如何实现的。

cookie签名、验证实现剖析

Express完成cookie值的签名,cookie-parser实现签名cookie的解析。两者共用同一个秘钥。

cookie签名

Express对cookie的设置(包括签名),都是通过res.cookie()这个方法实现的。

精简后的代码如下:

res.cookie = function (name, value, options) {
var secret = this.req.secret;
var signed = opts.signed; // 如果 options.signed 为true,则对cookie进行签名
if (signed) {
val = 's:' + sign(val, secret);
} this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); return this;
};

sign为签名函数。伪代码如下,其实就是把cookie的原始值,跟hmac后的值拼接起来。

敲黑板划重点:签名后的cookie值,包含了原始值。

function sign (val, secret) {
return val + '.' + hmac(val, secret);
}

这里的secret哪来的呢?是cookie-parser初始化的时候传入的。如下伪代码所示:

var cookieParser = function (secret) {
return function (req, res, next) {
req.secret = secret;
// ...
next();
};
}; app.use(cookieParser('secret'));

签名cookie解析

知道了cookie签名的机制后,如何"解析"签名cookie就很清楚了。这个阶段,中间件主要做了两件事:

  1. 将签名cookie对应的原始值提取出来
  2. 验证签名cookie是否合法

实现代码如下:

// str:签名后的cookie,比如 "s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"
// secret:秘钥,比如 "secret"
function signedCookie(str, secret) { // 检查是否 s: 开头,确保只对签过名的cookie进行解析
if (str.substr(0, 2) !== 's:') {
return str;
} // 校验签名的值是否合法,如合法,返回true,否则,返回false
var val = unsign(str.slice(2), secret); if (val !== false) {
return val;
} return false;
}

判断、提取cookie原始值比较简单。只是是unsign方法名比较有迷惑性。

一般只会对签名进行合法校验,并没有所谓的反签名。

unsign方法的代码如下:

  1. 首先,从传入的cookie值中,分别提取出原始值A1、签名值B1。
  2. 其次,用同样的秘钥对A1进行签名,得到A2。
  3. 最后,根据A2、B1是否相等,判断签名是否合法。
exports.unsign = function(val, secret){
var str = val.slice(0, val.lastIndexOf('.'))
, mac = exports.sign(str, secret); return sha1(mac) == sha1(val) ? str : false;
};

cookie签名的作用

主要是出于安全考虑,防止cookie被篡改,增强安全性。

举个小例子来看下cookie签名是如何实现防篡改的。

基于前面的例子展开。假设网站通过nick这个cookie来区分当前登录的用户是谁。在前面例子中,登录用户的cookie中,nick对应的值如下:(decode后的)

s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0

此时,有人试图修改这个cookie值,来达到伪造身份的目的。比如修改成xiaoming

s:xiaoming.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0

当网站收到请求,对签名cookie进行解析,发现签名验证不通过。由此可判断,cookie是伪造的。

hmac("xiaoming", "secret") !== "uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"

签名就一定能够确保安全吗

当然不是。

上个小节的例子,仅通过nick这个cookie的值来判断登录的是哪个用户,这是一个非常糟糕的设计。虽然在秘钥未知的情况下,很难伪造签名cookie。但用户名相同的情况下,签名也是相同的。这种情况下,其实是很容易伪造的。

另外,开源组件的算法是公开的,因此秘钥的安全性就成了关键,要确保秘钥不泄露。

还有很多,这里不展开。

小结

本文主要对Express + cookie-parser的签名和解析机制进行相对深入的介绍。

不少类似的总结文章中,把cookie的签名说成了加密,这是一个常见的错误,读者朋友需要注意一下。

签名部分的介绍,稍微涉及一些简单的安全知识,对这块不熟悉的同学可以留言交流。为讲解方便,部分段落、用词可能不够严谨。如有错漏,敬请指出。

相关链接

https://github.com/expressjs/cookie-parser

https://github.com/chyingp/nodejs-learning-guide

Express使用进阶:cookie-parser中间件实现深入剖析的更多相关文章

  1. express-12 Cookie与会话

    简介 HTTP是无状态协议.当浏览器中加载页面,然后转到同一网站的另一页面时,服务器和浏览器都没有任何内在的方法可以认识到,这是同一浏览器访问同一网站.换一种说法,Web工作的方式就是在每个HTTP请 ...

  2. express 最佳实践(二):中间件

    express 最佳实践(二):中间件 第一篇 express 最佳实践(一):项目结构 express 中最重要的就是中间件了,可以说中间件组成了express,中间件就是 express 的核心. ...

  3. Nodejs之express第三方核心模块的中间件——body-parser

    Node中的核心模块分两类:一类是自带的核心模块,如http.tcp等,第二类是第三方核心模块,express就是与http对应的第三方核心模块,用于处理http请求.express在3.0版本中自带 ...

  4. body-parser Node.js(Express) HTTP请求体解析中间件

    body-parser Node.js(Express) HTTP请求体解析中间件 2016年06月08日     781     声明 在HTTP请求中,POST.PUT和PATCH三种请求方法中包 ...

  5. django 2 ORM操作 ORM进阶 cookie和session 中间件

    ORM操作 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过使用描述 ...

  6. express 框架之 路由与中间件

    1.  什么是router路径,什么是middleware? 我们输入www.baidu.com 来访问百度的主页,浏览器会自动转换为 http://www.baidu.com:80/(省略一些参数) ...

  7. http协议、模块、express框架以及路由器、中间件和mysql模块

    一.http协议 是浏览器和web服务器之间的通信协议 1.通用头信息 request url:请求的url,向服务器请求的数据 request method:请求的方式   get.post sta ...

  8. session与cookie,django中间件

    0819自我总结 一.session与cookie 1.django设置session request.session['name'] = username request.session['age' ...

  9. express 洋葱模型 路由管理 中间件

    express 路由管理,通过 app.express();  app.METHOD(path,fn(req, res)的方式进行路由的配置.实现了请求的接口的路由的拆分.那么可以将路由配置,分发到不 ...

随机推荐

  1. hadoop的安装和配置(一)本地模式

    博主会用三篇文章来为大家详细的说明hadoop的三种模式: 本地模式 伪分布模式 完全分布模式 本地模式: 思路走向 |--------------------| | ①:配置Java环境  | | ...

  2. MapReduce工作原理流程简介

    在MapReduce整个过程可以概括为以下过程: 输入 --> map --> shuffle --> reduce -->输出 输入文件会被切分成多个块,每一块都有一个map ...

  3. Appium Mac 命令行安装

    试过很多方法都失败,打算用命令行方式安装. 准备工作: 从 https://github.com/appium/appium下载appium版本的源码(.tar) 详见github_Appium1.6 ...

  4. python3之装饰器

    1.装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景,比如:插入日志.性能测试.事务处 ...

  5. C++继承分析

    面向对象的三大特性之一就是继承,继承运行我么重用基类中已经存在的内容,这样就简化了代码的编写工作.继承中有三种继承方式即:public protected private,这三种方式规定了不同的访问权 ...

  6. js 移动端写搜索时怎么调用软键盘上面的搜索按钮

    这段时间一直在做移动端,所以遇到很多问题,现在很多网站在做移动端搜索的时候都不会在后面加一个搜索按钮,而是直接调用输入法上面的搜索搜索按钮进行搜索 input的一个新属性给我们提供非常方便的书写, 就 ...

  7. 【原】IOS兼容性之APP内fixed定位头部跳动

    兼容现象: 在App的webview里边,我们有时候会在页面里写自定义头部,会使用到fixed定位,我们想要的效果是,页面无论怎么滑动,这个自定义的头部始终是固定在顶部的,但是在ios 11以上的版本 ...

  8. 菜鸟VUER学习记——零0章、打开新的大门

    是什么 基于MVVM模型,核心库只关注视图层,通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件的js框架.根据项目的复杂度和需求,渐进的加入插件以达到恰到好处的程度. 解决问题 UI组件化 ...

  9. cain使用方法

    首先安装cain,这里就不废话了 1, 选择中核弹标志左边的按一下,然后在sniffer右键点击第一个扫描内网所有ip 扫描完后选择网关的IP下面选择arp标志,在按一下上方空格处,+号变得可以使用了 ...

  10. TagHelper+Layui封装组件之Radio单选框

    TagHelper+Layui封装组件之Radio单选框 标签名称:cl-radio 标签属性: asp-for:绑定的字段,必须指定 asp-items:绑定单选项 类型为:IEnumerable& ...