前言

最近在研究Express的源码,查看他的依赖项,关于cookie与session的依赖项一共有5个,这些依赖项具体都是做什么的呢,于是我就去研究了一下。

准备知识

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

当服务器收到HTTP请求时,服务器可以在响应头里面添加一个Set-Cookie选项。浏览器收到响应后通常会保存下Cookie,之后对该服务器每一次请求中都通过Cookie请求头部将Cookie信息发送给服务器。另外,Cookie的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。

Set-Cookie: <cookie名>=<cookie值>

返回信息如下:

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
...
[页面内容]

现在再向服务端发起请求则请求会如下:

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过Cookie传输,因为Cookie有其固有的不安全性,Secure 标记也无法提供确实的安全保障。从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记。

为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie,它们只应该发送给服务端。如果包含服务端 Session 信息的 Cookie 不想被客户端 JavaScript 脚本调用,那么就应该为其设置 HttpOnly 标记。

Domain 和 Path 标识定义了Cookie的作用域:即Cookie应该发送给哪些URL。

Domain 标识指定了哪些主机可以接受Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了Domain,则一般包含子域名。

例如,如果设置 Domain=mozilla.org,则Cookie也包含在子域名中(如developer.mozilla.org)。

Path 标识指定了主机下的哪些路径可以接受Cookie(该URL路径必须存在于请求URL中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。

例如,设置 Path=/docs,则以下地址都会匹配:

  • /docs
  • /docs/Web/
  • /docs/Web/HTTP

响应首部 Set-Cookie 被用来由服务器端向客户端发送 cookie。
语法:

Set-Cookie: <cookie-name>=<cookie-value>;[options] 

其中options可以有下列这些值:

  • Expires=<date>过期时间
  • Max-Age=<non-zero-digit>cookie失效之前需要经过的秒数
  • Domain=<domain-value>主机名
  • Path=<path-value>路径名
  • Secure一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器
  • HttpOnly设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie 属性、XMLHttpRequest 和 Request APIs 进行访问,以防范跨站脚本攻击(XSS)
  • SameSite允许服务器设定一则 cookie 不随着跨域请求一起发送

实例:

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

Express依赖项研究

这个库主要是用来解析或者序列化cookie的信息的,API也只有两个一个是cookie.parse(str, options),它的options只有一个选项decode用来规定采用何种解码方法,一个是cookie.serialize(name, value, options)它的options和set-cookie的可选项一致。

使用Nodejs自带的crypto模块签名和解析签名过的cookie,使用方法:

var cookie = require('cookie-signature');
var val = cookie.sign('hello', 'tobiiscool');
val.should.equal('hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI');
var val = cookie.sign('hello', 'tobiiscool');
cookie.unsign(val, 'tobiiscool').should.equal('hello');
cookie.unsign(val, 'luna').should.be.false;

cookie-parser依赖了cookiecookie-signature,如果用户在调用的时候传入了secret的话,则会生成signedCookie,否则为普通的json的cookie的键值对。

使用方法:

var express = require('express')
var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())

API:

  • cookieParser(secret, options)
    secret可以为数组或者字符串,options等同于cookie.parse传入的可选项
  • cookieParser.JSONCookie(str)
    将字符串解析为json形式
  • cookieParser.JSONCookies(cookies)
    将给定对象重复使用JSONCookie将value值替换为解析值
  • cookieParser.signedCookie(str, secret)
    解析signedCookie值
  • cookieParser.signedCookies(cookies, secret)
    将给定的对象重读使用signedCookie将value值替换为解析值

express-session

这个中间件将session保存在服务端,将对应的session ID保存在cookie中,默认地是将session保存在内存中,不能在生产环境中使用,会引起内存泄漏,可以选择使用session-store将之存储在数据库中。使用方法:

var session = require('express-session')
var express = require('express')
var app = express()
app.use(session({
    secret:"huxinmin", //签名使用
    cookie:{  //"参照cookie中的设置",
        domain:
        expires:
        httpOnly:
        maxAge:
        path:
        sameSite:
        secure:
    },
    genid:function(req){ //生成session ID的方法,默认使用uid-safe

    },
    name:"", //session ID的名字,默认是connect.sid
    proxy:undefined, //当设置了cookies secure选项时,信任的代理服务器
    resave:true, //强制保存session到session store即使没有任何改变
    rolling:false, //强制在每一次responce时重新设置cookie的识别符,例如maxAge
    saveUninitialized:true, //是否自动保存未初始化的会话
    store: new MemoryStore(), //session存储的实例
    unset:'keep', //响应结束后是否保存存储的session  
}))

创建成功后,就可以在req.session中访问到session了,同时它上面还有如下这些方法或属性:

  • regenerate(callback)重新生成session
  • destroy(callback)销毁
  • reload(callback)重载
  • save(callback)将session保存至store中
  • touch()更新.maxAge属性
  • id session ID的别名,req.sessionID中保存的是session ID
  • cookie伴随session的cookie

除了这些,你还可以自定义你自己的属性或方法,只要挂载到req.session上即可。

这个插件会将session的信息存储在cookie中,不过不能超过4KB大小。

使用方法:

var cookieSession = require('cookie-session')
var express = require('express')
var app = express()
app.use(cookieSession({
  name: 'session',
  keys: [/* secret keys */], //或者使用字符串的scret:""
  // Cookie 选项,除了包含常见的expires,path,domain,等还包括signed和overwrite
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
  signed:true,//是否签名的cookie
  overwrite:true //设置cookie之前是否重写
}))

然后你就能在req.session中访问到了创建的session,并且还有下面几个属性:

  • req.sessionOptions.isChanged判断session是否变化
  • req.sessionOptions.isNew判断session是否是新的
  • req.sessionOptions.isPopulated判断session是否有填充数据
  • req.sessionOptions创建session时的选项的一个浅拷贝
  • req.session = null可以销毁session

参考资料