深入剖析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 相关文章推荐
json 定义
Jun 10 Javascript
JQuery 图片延迟加载并等比缩放插件
Nov 09 Javascript
克隆javascript对象的三个方法小结
Jan 12 Javascript
autoIMG 基于jquery的图片自适应插件代码
Mar 12 Javascript
jquery计算鼠标和指定元素之间距离的方法
Jun 26 Javascript
JavaScript使用encodeURI()和decodeURI()获取字符串值的方法
Aug 04 Javascript
jQuery之简单的表单验证实例
Jul 07 Javascript
js模糊查询实例分享
Dec 26 Javascript
vue.js 获取select中的value实例
Mar 01 Javascript
详解微信小程序缓存--缓存时效性
May 02 Javascript
vue实现带过渡效果的下拉菜单功能
Feb 19 Javascript
idea编译器vue缩进报错问题场景分析
Jul 04 Vue.js
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
冰滴咖啡制作步骤
2021/03/03 冲泡冲煮
php知道与问问的采集插件代码
2010/10/12 PHP
php中array_column函数简单实现方法
2016/07/11 PHP
Zend Framework入门教程之Zend_Registry组件用法详解
2016/12/09 PHP
让Laravel API永远返回JSON格式响应的方法示例
2018/09/05 PHP
jquery制作居中遮罩层效果分享
2014/02/21 Javascript
深入分析Javascript跨域问题
2015/04/17 Javascript
js计算文本框输入的字符数
2015/10/23 Javascript
基于jQuey实现鼠标滑过变色(整行变色)
2015/12/07 Javascript
关于backbone url请求中参数带有中文存入数据库是乱码的快速解决办法
2016/06/13 Javascript
深入理解jQuery()方法的构建原理
2016/12/05 Javascript
js仿京东轮播效果 选项卡套选项卡使用
2017/01/12 Javascript
jQuery.ajax向后台传递数组问题的解决方法
2017/05/12 jQuery
js实现左右两侧浮动广告
2018/07/09 Javascript
vue-cli 3.0 版本与3.0以下版本在搭建项目时的区别详解
2018/12/11 Javascript
Vue.js实现开发购物车功能的方法详解
2019/02/22 Javascript
如何修改Vue打包后文件的接口地址配置的方法
2020/04/22 Javascript
vue 获取元素额外生成的data-v-xxx操作
2020/09/09 Javascript
Python 安装setuptools和pip工具操作方法(必看)
2017/05/22 Python
对Tensorflow中权值和feature map的可视化详解
2018/06/14 Python
Python实现队列的方法示例小结【数组,链表】
2020/02/22 Python
基于Python计算圆周率pi代码实例
2020/03/25 Python
详解python百行有效代码实现汉诺塔小游戏(简约版)
2020/10/30 Python
python3访问字典里的值实例方法
2020/11/18 Python
美国大型的健身社区和补充商店:Bodybuilding.com
2016/09/06 全球购物
美国医疗用品、医疗设备和家庭保健用品商店:Medical Supply Depot
2018/07/08 全球购物
当我正在为表建立索引的时候,SQL Server 会禁止对表的访问吗
2014/04/28 面试题
外贸英语专业求职信范文
2013/12/25 职场文书
大学生职业规划论文
2014/01/11 职场文书
2014年重阳节老干部座谈会局领导发言稿
2014/09/25 职场文书
党的群众路线学习笔记
2014/11/06 职场文书
好媳妇事迹材料
2014/12/24 职场文书
2016年社区服务活动总结
2016/04/06 职场文书
哪类餐饮行业,最适合在高校创业?
2019/08/19 职场文书
Python函数中apply、map、applymap的区别
2021/11/27 Python
Mysql外键约束的创建与删除的使用
2022/03/03 MySQL