Express教程05:Cookie

6,042 阅读3分钟

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

Cookie介绍

Cookie存储在浏览器,在浏览器请求服务器时,其中的数据都会被发送到服务端,常用来做用户信息校验等。

但由于Cookie存储在浏览器,容易受到篡改,安全性较差。

使用cookie-parser处理Cookie

处理Cookie,可以使用中间件cookie-parser

读取Cookie

示例代码:/lesson05/server.js

使用cookie-parser中间件时,需要先通过server.use(cookieParser())解析cookie,之后就可以在req.cookies属性中读取到cookie的值。

在浏览器打开http://localhost:8080/cookie,在控制台写入cookie{"userName":"lee"}

// 使用cookie-parser中间件,解析Cookie
server.use(cookieParser())

server.get('/cookie', (req, res, next) => {
  // 读取cookieParser解析的Cookie
  console.log(req.cookies)

  res.send(`cookies: ${JSON.stringify(req.cookies)}`)
})

在浏览器打开http://localhost:8080/cookie,服务端打印结果为:{"userName":"lee"}

设置Cookie

示例代码:/lesson05/server.js

设置Cookie可以用Express自带的方法res.cookie

方法的第一个参数为设置的属性名,第二个参数为属性值,第三个参数为配置项,例如:

server.get('/cookie', (req, res, next) => {
  // express自带的设置Cookie方法
  res.cookie('userName', 'lee', {
    // 设置该Cookie只可以由服务端访问,即前端JavaScript无法访问document.cookie获取该值,但控制台还是可以查看和修改
    httpOnly: true,
    // 只有通过HTTPS请求的Cookie才被使用,否则都认为是错误的Cookie
    // secure: true,
    // 设置保存Cookie的域名,浏览器查找Cookie时,子域名(如translate.google.com)可以访问主域名(google.com)下的Cookie,而主域名(google.com)不可以访问子域名(如translate.google.com)下的Cookie
    // 本地测试可直接设置为localhost
    domain: 'localhost',
    // 设置保存Cookie的路径,浏览器查找Cookie时,子路径(如/map)可以访问根路径('/')下设置的Cookie,而根路径('/')无法访问子路径(如/map)下设置的Cookie
    path: '/',
    // 通过expires设置Cookie过期时间为14天后
    // expires: new Date(new Date().getTime() + 14 * 86400000),
    // 通过maxAge设置Cookie过期时间为14天后
    maxAge: 14 * 86400000,
  })

  // 读取cookieParser解析的Cookie
  console.log(req.cookies)

  res.send(`cookies: ${JSON.stringify(req.cookies)}`)
})

在浏览器的控制台中,可以看到设置的Cookie为{"userName":"lee"},有效期是14天。

Cookie的签名

示例代码:/lesson05/server.js

Cookie的签名即是使用一个存储在服务端的密钥对Cookie进行加密,Cookie中存储的数据是经过密钥加密的,因此客户端如果对Cookie进行修改,服务端校验就无法通过。

设置密钥

若需要给Cookie进行签名,首先需要给cookieParser的第一个参数传入一个字符串密钥:

// 解析Cookie
server.use(cookieParser(
  // 签名用密钥,需要保密,仅存储在服务端
  'NpLRTpy1vbBzEw2JcAxpf970kOk2RViDn5wKwrMv'
))

签名Cookie

这样就可以开始设置签名Cookie了,只需要在res.cookie方法的配置参数中设置signed: true属性:

res.cookie('password', 'test123', {
  httpOnly: true,
  domain: 'localhost',
  path: '/',
  maxAge: 14 * 86400000,
  // 开启该Cookie的签名模式
  signed: true
})

在浏览器打开http://localhost:8080/cookie,可以看到Cookie中被设置了password属性,其值为s%3Atest123.HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg

如果用decodeURIComponent方法进行解码,结果为s:test123.HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg

其意义如下:

  1. s表示该Cookie为签名Cookie
  2. test123表示该Cookie设置的值
  3. HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg表示对该值的签名,也就是说当服务端接收到该Cookie时,会使用服务端的密钥对test123进行签名,再与HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg进行对比,如果正确才可以使用。

此时可以看到服务端打印结果为signedCookies: {"password":"test123"}

在客户端修改签名的Cookie

如果用户在客户端对签名的Cookie进行了修改,例如在浏览器控制台,将password改为s%3Atest456.HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg

此时在服务端打印的结果为signedCookies: {"password":false},表示校验失败。

同时浏览器中的Cookie值被重新修改为了s%3Atest123.HrZ44MCUeLXj0uZAzTpCXWduflOsmfBs5XsuK4eTMvg

除非用户在修改签名的Cookie的值时,将该值的签名一起修改,否则校验是无法通过的。

但由于签名是由服务端的密钥计算而成,因此这个值通常是安全的。

不过,因为对Cookie进行签名,会占用更多的Cookie存储空间,而Cookie在浏览器中最多只能存储4K,所以签名不可滥用,只用来保护重要的数据即可。