第一步:我们来学习一下cookie-signature:

var cookie=require('./index');
var val = cookie.sign('hello', 'tobiiscool');
console.log(val);
//打印hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI
var unsign=cookie.unsign(val, 'tobiiscool')
console.log(unsign);
//打印hello

我们再来看看他在内部是怎样进行加密的:

exports.sign = function(val, secret){
if ('string' != typeof val) throw new TypeError("Cookie value must be provided as a string.");
if ('string' != typeof secret) throw new TypeError("Secret string must be provided.");
//最后加密过的字符串是中间包括一个点的字符串,是通过sha256来进行编码的
return val + '.' + crypto
.createHmac('sha256', secret)
.update(val)
.digest('base64')
.replace(/\=+$/, '');
};

通过这部分的代码我们知道加密后的字符串在点签名是明文,后面是经过sha256编码的密文。

那么我们接下来我们看看怎样进行解码:

//也就是说调用unsign方法的时候 var unsign=cookie.unsign(val, 'tobiiscool')首先是获取到点号前面的明文,然后对这个明文进行一次加密,加密结果是:"明文+sha256编码的密文"
exports.unsign = function(val, secret){
if ('string' != typeof val) throw new TypeError("Signed cookie string must be provided.");
if ('string' != typeof secret) throw new TypeError("Secret string must be provided.");
//加密后的cookie是这样的类型的:hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI
var str = val.slice(0, val.lastIndexOf('.'))
//获取最后一个点号前面的字符作为我们要解密的cookie的签名
, mac = exports.sign(str, secret);
//然后对传入的解密的val和刚才通过明文加密的结果进行对象,假设同样就是true,否则就是false
return sha1(mac) == sha1(val) ? str : false;
};
//对string进行sha1算法
function sha1(str){
return crypto.createHash('sha1').update(str).digest('hex');
}

我们能够非常清楚的看到在解密时候首先获取到明文,也就是点前面的一部分,然后对这部分进行一次加密。这应该是期望的加密的结果。然后我们把期望的加密结果和用户传入的加密结果进行一次同样的sha1算法,比較两者的结果,假设同样表示成功!

第二步:我们来学习cookie插件

/*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/ /**
* Module exports.
* @public
*/ exports.parse = parse;
exports.serialize = serialize; /**
* Module variables.
* @private
*/ var decode = decodeURIComponent;
var encode = encodeURIComponent;
var pairSplitRegExp = /; */; /**
* RegExp to match field-content in RFC 7230 sec 3.2
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
*/ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; /**
* Parse a cookie header.
*
* Parse the given cookie header string into an object
* The object has the various cookies as keys(names) => values
*
* @param {string} str
* @param {object} [options]
* @return {object}
* @public
*/
//cookie.parse(cookies, options);当中cookies參数是从req.headers.cookie中获取到的!
function parse(str, options) {
//參数必须是string类型
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string');
}
var obj = {}
var opt = options || {};
//options默认是一个空对象,用户也能够自己传入
var pairs = str.split(pairSplitRegExp);
//var pairSplitRegExp = /; */;进行cookie分解
var dec = opt.decode || decode;
//dec表示用户自己传入的解密cookie的函数,默认是decodeURIComponent
pairs.forEach(function(pair) {
//接下来遍历我们通过正則表達式匹配出来的cookie!
var eq_idx = pair.indexOf('=')
//获取=号的下标
// skip things that don't look like key=value
if (eq_idx < 0) {
return;
}
var key = pair.substr(0, eq_idx).trim()
var val = pair.substr(++eq_idx, pair.length).trim();
//获取cookie的key和value值
// quoted values
//假设cookie的值是一个用引號括起来的字符串,这时候就就把前后的引號分割掉!
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
//通过用户传入的options中的decode函数对cookie的值进行解密
obj[key] = tryDecode(val, dec);
}
});
return obj;
}
/**
* Serialize data into a cookie header.
*
* Serialize the a name value pair into a cookie string suitable for
* http headers. An optional options object specified cookie parameters.
*
* serialize('foo', 'bar', { httpOnly: true })
* => "foo=bar; httpOnly"
*
* @param {string} name
* @param {string} val
* @param {object} [options]
* @return {string}
* @public
*/ function serialize(name, val, options) {
var opt = options || {};
var enc = opt.encode || encode; if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
var value = enc(val); if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
var pairs = [name + '=' + value];
if (null != opt.maxAge) {
var maxAge = opt.maxAge - 0;
if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
pairs.push('Max-Age=' + Math.floor(maxAge));
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
pairs.push('Domain=' + opt.domain);
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
} pairs.push('Path=' + opt.path);
} if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
if (opt.httpOnly) pairs.push('HttpOnly');
if (opt.secure) pairs.push('Secure');
if (opt.firstPartyOnly) pairs.push('First-Party-Only'); return pairs.join('; ');
} /**
* Try decoding a string using a decoding function.
*
* @param {string} str
* @param {function} decode
* @private
*/ function tryDecode(str, decode) {
try {
//直接调用decode就能够了
return decode(str);
} catch (e) {
return str;
}
}

这个插件的功能是比較简单的,就是把用户传入的cookie字符串解析为一个对象,并且options中的decode默认是经过decodeURIComponent处理过的!

最后一步:弄清楚cookie-parser

'use strict';
var cookie = require('cookie');
var signature = require('cookie-signature');
module.exports = cookieParser;
module.exports.JSONCookie = JSONCookie;
module.exports.JSONCookies = JSONCookies;
module.exports.signedCookie = signedCookie;
module.exports.signedCookies = signedCookies;

非常显然这个模块依赖于我们前面引入的cookie和cookie-signature模块。

我们首先来分析cookieParser方法:

function cookieParser(secret, options) {
return function cookieParser(req, res, next) {
//假设req.cookies已经存在那么直接调用以下一个中间件
if (req.cookies) {
return next();
}
//获取req.headers.cookie中保存的cookie
var cookies = req.headers.cookie;
//假设secret不存在。或者存在了是一个数组那么都会变成一个数组,不存在就是空数组。 假设不过一个string那么secrets就会变成一个只包括这个string的数组
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret];
//把第一个參数封装到req.secret中
req.secret = secrets[0];
//初始化req.cookies为一个空对象
req.cookies = Object.create(null);
//初始化req.signedCookies也是一个空对象
req.signedCookies = Object.create(null);
//假设请求头中没有cookie那么也用不着对cookie进行加密
// no cookies
if (!cookies) {
return next();
}
//假设请求头中也存在cookie,那么把req.headers.cookie中的cookie进行解析,当中options就是传入的options參数。然后把解析结果封装到req.cookies上
req.cookies = cookie.parse(cookies, options);
// parse signed cookies
//解析签名后的cookie,由于用户提供了秘钥把解析的结果封装到 req.signedCookies上面,传入的參数是解析出来的cookies并被封装到req.cookies上面
if (secrets !== 0) {
//上面是对cookie进行解析。解析成为键值对的形式。这里是通过secret对cookie进行签名
req.signedCookies = signedCookies(req.cookies, secrets);
req.signedCookies = JSONCookies(req.signedCookies);
}
// parse JSON cookies
req.cookies = JSONCookies(req.cookies);
next();
};
}

非常显然。首先通过引入cookie模块把req.headers.cookies的字符串解析为一个对象,接着调用signedCookies方法,我们看看signedCookies方法在干什么?

function signedCookies(obj, secret) {
var cookies = Object.keys(obj);
//获取全部的键名
var dec;
var key;
var ret = Object.create(null);
var val;
//对键名进行迭代
for (var i = 0; i < cookies.length; i++) {
key = cookies[i];
val = obj[key];
//当中key是cookie的键,而val是cookie的值,然后调用signedCookie方法通过secret对cookie进行解密
dec = signedCookie(val, secret);
//解密后的cookie怎样和解密前的cookie是一样的那么表示压根就不须要解密,仅仅有须要被解密的cookie最后才会被返回!
if (val !== dec) {
ret[key] = dec;
delete obj[key];
}
}
return ret;
}

这种方法的作用是解析加密后的cookie,然后返回key/value模式。当中调用了signedCookie对每个值进行单独的解析和推断:

function signedCookie(str, secret) {
//假设要签名的值不是string那么直接返回undefined
if (typeof str !== 'string') {
return undefined;
}
//假设要签名的值前两个不是s:那么返回原字符串
if (str.substr(0, 2) !== 's:') {
return str;
}
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret];
//把secret转化为一个数组,假设没有传入就是空数组,假设传入的字符串那么就变成仅仅有一个元素的数组!
for (var i = 0; i < secrets.length; i++) {
var val = signature.unsign(str.slice(2), secrets[i]);
//对值进行解析,signature.unsign第一个參数是去除前两个字符也就是"s:"后的值,第二个參数是数组中的每个元素。因此每个元素都会成为秘钥
//假设解析出来的val值不是false那么返回解析出来的值,否则返回false。也就是仅仅要有一个解析出来的值不是false那么后面的秘钥就不会被使用了!
if (val !== false) {
return val;
}
}
return false;
}

这种方法的作用是返回解密后的cookie的值。传入的第一个參数str表示的是从client获取到的加密后的cookie,可是首先要分割掉前面的"s:"。接着调用前面的cookie-signature的unsign方法对cookie的有效性进行推断,假设返回的不是false表示这个cookie是有效的,这时候返回解密后的cookie就能够了,所以这时候server端获取到的就是解密的cookie的值。

另一点。在signedCookie假设这个cookie是有效的那么就返回解密后的值。否则就返回false,进而到signedCookies中进行处理,把解密后的cookie封装到特定的key上面!

接着我们回到上面的这一部分逻辑,弄清楚它在干什么?

 if (secrets !== 0) {
//上面是对cookie进行解析,解析成为键值对的形式,这里是通过secret对cookie进行签名
req.signedCookies = signedCookies(req.cookies, secrets);
req.signedCookies = JSONCookies(req.signedCookies);
}
// parse JSON cookies
req.cookies = JSONCookies(req.cookies);
next();

我们把解密后的cookie封装到req.signedCookies域中。接着又对当中的cookie进行了特殊的处理,也就是经过JSONCookies,我们看看这种方法在做什么?

function JSONCookies(obj) {
var cookies = Object.keys(obj);
var key;
var val;
for (var i = 0; i < cookies.length; i++) {
key = cookies[i];
//获取key值
val = JSONCookie(obj[key]);
//对每个value值调用JSONCookie方法,假设返回了值那么更新特定的key相应的value值!
if (val) {
obj[key] = val;
}
}
return obj;
}

接着我们看看JSONCookie在干吗?

function JSONCookie(str) {
if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') {
return undefined;
}
//从这个逻辑非常easy推断出什么是JSONCookie(ven图),也就是必须是值为string同一时候前两个字符为"j:"才会进行特殊的处理
try {
return JSON.parse(str.slice(2));
//否则通过JSON.parse进行处理
} catch (err) {
return undefined;
}
}

总之:假设对签名的cookie进行了解析后假设是JSONCookie那么就会进行JSON.parse处理!并且我们通过代码非常easy看到在req对象上绑定了例如以下的属性:

req.secret:传入的秘钥用于对cookie进行加密

req.cookies:对req.headers.cookie中的cookie进行解析,返回的一个对象

req.signedCookies:保存的是解析后的cookie的真实值。可是可能还会被JSONCookie进行处理

我们再来看看cookie插件中的serialize方法:

//var hdr = cookie.serialize('foo', 'bar');
function serialize(name, val, options) {
var opt = options || {};
var enc = opt.encode || encode;
//options对象的encode函数
if (!fieldContentRegExp.test(name)) {
//var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
throw new TypeError('argument name is invalid');
}
var value = enc(val);
//var encode = encodeURIComponent;对value值进行encodeURIComponent操作
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
//非常显然没有对name进行encodeURIComponent。仅仅要name在特定的Unicode区间就能够了
var pairs = [name + '=' + value];
//指定maxAge
if (null != opt.maxAge) {
var maxAge = opt.maxAge - 0;
if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
pairs.push('Max-Age=' + Math.floor(maxAge));
}
//指定domian
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
pairs.push('Domain=' + opt.domain);
}
//指定path
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}
pairs.push('Path=' + opt.path);
}
//指定expires
if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
if (opt.httpOnly) pairs.push('HttpOnly');
if (opt.secure) pairs.push('Secure');
if (opt.firstPartyOnly) pairs.push('First-Party-Only');
//不知道First-Party-Only是干嘛的,并且这个头还不是键值对,而是与httponly一样
return pairs.join('; ');
}

注意事项:前面说了要分割掉cookie中的"s:",设置为什么呢?我们来看看使用了cookie-parser后cookie格式是怎么样的?

Cookie:qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg; blog=s%3A-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o.Axjo6YmD2dLPGQK9aD1mR8FcpOzyHaGG6cfGUWUVK00

我们把前面的qinliang部分的cookie进行一下decodeURIComponent处理:

console.log(decodeURIComponent('s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg'));

这时候就会发现打印例如以下的结果了:

s:BDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg

这也是为什么前面说要分割掉前面的"s:"的原因!

当中有两点须要细致考虑:

(1)cookie的name和value有一个正則表達式范围

var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;

(2)First-Party-Only

 if (opt.firstPartyOnly) pairs.push('First-Party-Only');

Cookie-Parser是怎样解析签名后的cookie的(同一时候对cookie和cookie-signature进行说明)的更多相关文章

  1. C#实现.Net对邮件进行DKIM签名和验证,支持附件,发送邮件签名后直接投递到对方服务器(无需己方邮件服务器)

    项目地址 https://github.com/xiangyuecn/DKIM-Smtp-csharp 主要支持 对邮件进行DKIM签名,支持带附件 对整个邮件内容(.eml文件)的DKIM签名进行验 ...

  2. jquery.cookie实战用法详细解析

    Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是 ...

  3. MinerUrl.java 解析页面后存储URL类

    MinerUrl.java 解析页面后存储URL类 package com.iteye.injavawetrust.miner; /** * 解析页面后存储URL类 * @author InJavaW ...

  4. jquery.cookie广告弹窗点击关闭后一天弹一次

    jquery.cookie广告弹窗点击关闭后一天弹一次 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...

  5. .Net Core 认证系统之Cookie认证源码解析

    接着上文.Net Core 认证系统源码解析,Cookie认证算是常用的认证模式,但是目前主流都是前后端分离,有点鸡肋但是,不考虑移动端的站点或者纯管理后台网站可以使用这种认证方式.注意:基于浏览器且 ...

  6. requests接口自动化4-登录后才能访问的get请求,需共享cookie

    登录后才能访问的get请求,需共享cookie fiddler里请求响应结果: 代码: import requests import json #form表单形式的post请求,用data传参,Con ...

  7. 使用阿里云OSS的服务端签名后直传功能

    网站一般都会有上传功能,而对象存储服务oss是一个很好的选择.可以快速的搭建起自己的上传文件功能. 该文章以使用阿里云的OSS功能为例,记录如何在客户端使用阿里云的对象存储服务. 服务端签名后直传 背 ...

  8. 关于IPA文件重签名后如何跟踪管理APP的技术探讨和实践演示

    前言:开发iOS的朋友都知道,在功能开发完成后,我们就会用申请的苹果账号在后台做证书配置,然后提交到AppStore,但是也有部分APP我们不需要提交到AppStore,比如内部测试用的APP.定制给 ...

  9. Java web Cookie详解(持久化+原理详解+共享问题+设置中文+发送多个Cookie)

    Java web Cookie详解 啥是cookie? 查询有道词典得: web和饼干有啥关系? 这个谜底等等来为大家揭晓 会话技术 web中的会话技术类似于生活中两个人聊天,不过web中的会话指的是 ...

随机推荐

  1. hdu 5067(暴力搜索)

    Harry And Dig Machine Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Ot ...

  2. 华农oj Problem K: 负2进制【有技巧构造/待补】

    Problem K: 负2进制 Time Limit: 2 Sec Memory Limit: 128 MB Submit: 51 Solved: 6 [Submit][Status][Web Boa ...

  3. HttpRunner 接口自动化测试进阶

    前面说到了httprunner的安装与简单使用,参见: https://www.cnblogs.com/chengtch/p/8735160.html 这里我们介绍一下通过调试源码的方式来做接口测试: ...

  4. python获取小程序手机号并绑定

    最近在做小程序开发,在其中也遇到了很多的坑,获取小程序的手机号并绑定就遇到了一个很傻的坑. 流程介绍 官方流程图 小程序使用方法 需要将 <button> 组件 open-type 的值设 ...

  5. Tarjan缩点【p4819】[中山市选]杀人游戏

    Description 一位冷血的杀手潜入Na-wiat,并假装成平民.警察希望能在\(N\)个人里面,查出谁是杀手.警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀 ...

  6. KMP CF126B Password

    Description Asterix,Obelix和他们的临时伙伴Suffix.Prefix已经最终找到了和谐寺.然而和谐寺大门紧闭,就连Obelix的运气也没好到能打开它. 不久他们发现了一个字符 ...

  7. sharepoint 2013 和 office web apps server 2013集成

    环境: 三台服务器  系统:window 2008 R2server01: 192.168.10.162(office web app)server02: 192.168.10.163(AD)serv ...

  8. Flash3D学习计划(一)——3D渲染的一般管线流程

    一:什么是渲染管线 渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元.一个流水线是一序列可以并行和按照固定顺序进行的阶段.每个阶段都从它的前一阶段接收输入,然后把输出发给随后 ...

  9. 【NOIP模拟赛】【数学真奇妙系列】纸盒子

    Task 1.纸盒子(box.pas/box.c/box.cpp) [题目描述] Mcx是一个有轻度洁癖的小朋友.有一天,当他沉溺于数学卷子难以自拔的时候,恍惚间想起在自己当初学习概率的时候准备的一堆 ...

  10. JAVA和.NET工作流相关项目收集

    .NET以自主实现为主, 暂未发现使用WWF框架开发的开源工作流,    java以BPM系为主 . .NET: RoadFlow : http://www.cqroad.cn/ 使用了百度编辑器扩展 ...