深入剖析Express cookie-parser中间件实现示例


Posted in Javascript onFebruary 01, 2018

文章导读

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
Nov 09 Javascript
jquery自定义插件——window的实现【示例代码】
May 06 Javascript
Bootstrap框架下下拉框select搜索功能
Mar 26 Javascript
一个仿微博登陆邮箱提示框js开发案例
Jul 28 Javascript
原生js实现中奖信息无间隙滚动效果
Jan 18 Javascript
vue-dialog的弹出层组件
May 25 Javascript
Vue插件写、用详解(附demo)
Mar 20 Javascript
JavaScript使用readAsDataURL读取图像文件
May 10 Javascript
微信小程序多列选择器range-key使用详解
Mar 30 Javascript
在 Typescript 中使用可被复用的 Vue Mixin功能
Apr 17 Javascript
JavaScript中变量提升机制示例详解
Dec 27 Javascript
es6中new.target的作用和使用场景简单示例分析
Mar 14 Javascript
JS 实现百度搜索功能
Feb 01 #Javascript
用node-webkit把web应用打包成桌面应用(windows环境)
Feb 01 #Javascript
微信小程序日期时间选择器使用方法
Feb 01 #Javascript
NW.js 简介与使用方法
Feb 01 #Javascript
原生JS实现的多个彩色小球跟随鼠标移动动画效果示例
Feb 01 #Javascript
Vue组件之自定义事件的功能图解
Feb 01 #Javascript
微信小程序三级联动选择器使用方法
May 19 #Javascript
You might like
php制作动态随机验证码
2015/02/12 PHP
JavaScript继承方式实例
2010/10/29 Javascript
javascript中interval与setTimeOut的区别示例介绍
2014/03/14 Javascript
jQuery中hasClass()方法用法实例
2015/01/06 Javascript
JS数组的常见用法实例
2015/02/10 Javascript
jquery实现图片上传之前预览的方法
2015/07/11 Javascript
Bootstrap轮播插件使用代码
2016/10/11 Javascript
微信小程序 Nginx环境配置详细介绍
2017/02/14 Javascript
Angular排序实例详解
2017/06/28 Javascript
js实现随机点名小功能
2017/08/17 Javascript
ES6使用Set数据结构实现数组的交集、并集、差集功能示例
2017/10/31 Javascript
angular1配合gulp和bower的使用教程
2018/01/19 Javascript
Javasript设计模式之链式调用详解
2018/04/26 Javascript
Vue中 v-if/v-show/插值表达式导致闪现的原因及解决办法
2018/10/12 Javascript
vue实现自定义H5视频播放器的方法步骤
2019/07/01 Javascript
微信接入之获取用户头像的方法步骤
2019/09/23 Javascript
vue中echarts图表大小适应窗口大小且不需要刷新案例
2020/07/19 Javascript
js实现微信聊天界面
2020/08/09 Javascript
详解Vue中的watch和computed
2020/11/09 Javascript
[03:56]还原FTP电影首映式 DOTA2群星拼出遗迹世界
2014/03/26 DOTA
python轻松实现代码编码格式转换
2015/03/26 Python
用Python程序抓取网页的HTML信息的一个小实例
2015/05/02 Python
解决Matplotlib图表不能在Pycharm中显示的问题
2018/05/24 Python
python分块读取大数据,避免内存不足的方法
2018/12/10 Python
python程序快速缩进多行代码方法总结
2019/06/23 Python
PYQT5开启多个线程和窗口,多线程与多窗口的交互实例
2019/12/13 Python
手把手教你如何用Pycharm2020.1.1配置远程连接的详细步骤
2020/08/07 Python
Python如何获取文件路径/目录
2020/09/22 Python
豆腐の盛田屋官网:日本自然派的豆乳面膜、肥皂、化妆水、乳液等
2016/10/08 全球购物
《赵州桥》教学反思
2014/02/17 职场文书
导航工程专业自荐信
2014/09/02 职场文书
党员三严三实心得体会
2014/10/13 职场文书
万能检讨书2000字
2014/10/17 职场文书
基督教追悼会答谢词
2015/09/29 职场文书
标准发言稿结尾
2019/07/18 职场文书
浅析MongoDB之安全认证
2021/06/26 MongoDB