主题
Cookie鉴权
提示
HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上(浏览器也会遵循协议,只在访问符合 Cookie 指定规则的网站时带上对应的 Cookie 来保证安全性)。
一. 原生nodejs使用cookie
1 一些原生nodejs设置cookie的API
1.1 通过服务端设置cookie
js
response.setHeader('Set-Cookie', [`name=MrLi; httpOnly=true; max-age=10`, 'age=12']);
注意
- 需要将要设置的cookie放入数组中,再统一执行
Set-Cookie
,若多次执行这句命令,后面的回覆盖前面的。 - Cookie里面的每段内容是通过
分号+空格
来进行分割的例如: - 若是我们要对某一个cookie键值对设置他参数,在数组内的对应cookie键值对中通过
分号+空格
进行分割,比如设置只读,设置可访问的域等等参数
1.2 服务端读取请求头里面的cookie
js
let cookies = request.headers.cookie
// 会得到请求投中cookie字段的值,是一个string,如上图内的cookie值类似。
1.3 利用nodejs提供的queryString
模块,解析cookie值为对象
js
const querystring = require('querystring');
let cookiesObj = querystring.parse(req.headers.cookie, '; ', '=');
// {name:"MrLi",age:"12"}
2 服务端设置cookie时候的其他常用参数
首先,来看一下cookie里面有哪些可以设置的参数,我们来看一下百度的cookie:
name
:cookie的名字value
:name对应的值domain
:域名设置(默认就是当前域名)这里的域名指的是父域和子域,不能跨域设置,限制哪些域下可以访问这个cookie,若设置为父级域名,则父、子域名都可以访问,若设置的是子级域名,则只有子域名及子域名的子域名才能访问,.baidu.com
是www.baidu.com
的父级域名path
:cookie在哪些路径下可以写入(很少用,默认为/
),同express中间件原理一致,/
表示是由以/开头的路径都可以写入exipres
或者max-age
:类似于http缓存的过期时间,过期会自动删除cookie,默认是浏览器一关就过期,就是图片里面写的Session
。exipres
是绝对世间,截止到末年某月某日过期,例如 10秒后过期的绝对时间exipres=new Date(Date.now() + 10*1000).toGMTString()
max-age
是相对世间,多少秒后过期,(单位秒)[常用]
httpOnly
:是否客户端可以操纵cookie,若设置为true,则在客户端无法通过代码操纵cookie,但是可以在浏览器控制台手动修改它。
其他参数不常用,不一一列举了
3 原生Nodejs代码示例
js
const http = require('http');
const querystring = require('querystring');
http.createServer((req, res) => {
// 通过服务端去写入cookie
if (req.url == '/read') {
// 读取cookie
res.end(JSON.stringify(querystring.parse(req.headers.cookie, '; ', '=')));
} else if (req.url == '/write') {
// 设置cookie
// 这里设置了两个cookie,name和 age,其中name为客户端只读,有效期10秒
res.setHeader('Set-Cookie', [`name=MrLi; httpOnly=true; max-age=10`, 'age=18']);
res.end('cookie write success')
} else {
res.end('Not Found')
}
}).listen(3000);
效果
cookie只读效果(name是只读的,无法通过客户端代码访问到,也无法通过代码修改)
二. 提高cookie安全性
由于cookie在客户端是可以随意修改的, 即使设置了httpOnly,也可以手动修改,这很不安全,有一种解决方法,可以提高cookie被更改的风险
当给浏览器设置cookie时,可以增加签名,根据数据内容创建一个唯一的签名,利用的是MD5
,MD5的也叫加盐算法,是根据内容和秘钥算出一个签名(且不能反解),若是相同的秘钥和内容签名的结果是相同的。每次服务端拿到cookie的时候,就对比这两个签名是否一直,若不一致就说明已经被更改了。
这也是Express和koa提高cookie安全性的方法
js
const crypto = require('crypto');
const secret = 'fdkjgmvmlsdjvmv;cmx'; // 秘钥
const sign = (value)=>{
let str = crypto.createHmac('sha256',secret).update(value).digest('base64');
return str.replace(/\/|\=|\+/,'') // 需要将base64里面的/ = +这些符号替换掉,否则传输时候不识别
}
具体用法见下文
base64URL详解:
通过上述的增加签名后,cookie会稍微变得安全一些,但是cookie始终是明文的,不能在里面放一些敏感信息,比如用户名密码等
三. 封装getCookie 和setCookie方法,很方便的操作cookie
js
const http = require('http');
const querystring = require('querystring');
const crypto = require('crypto');
const secret = 'erwefdcvdbvcxddfdfdf'; // 秘钥
const sign = (value)=>{
let str = crypto.createHmac('sha256',secret).update(value).digest('base64');
return str.replace(/\/|\=|\+/,'')
}
http.createServer((req, res) => {
req.getCookie = function (key,options = {}) {
let cookieObj = querystring.parse(req.headers.cookie, '; ', '=');
if(options.signed){
let [value,s] = (cookieObj[key]||'').split('.');
let newSign = sign(value);
if(newSign === s){
return value; // 签名一致 说明这次的内容是没有被改过的
}else{
return undefined; // 签名被篡改了 不能使用了
}
}
return cookieObj[key];
}
let arr = [];
res.setCookie = function (key,value,options = {}) {
let opts = [];
if(options.domain){
opts.push(`domain=${options.domain}`);
}
if(options.path){
opts.push(`path=${options.path}`)
}
if(options.maxAge){
opts.push(`max-age=${options.maxAge}`)
}
if(options.httpOnly){
opts.push(`httpOnly=${options.httpOnly}`)
}
if(options.signed){
value = value + '.' + sign(value); // 若设置signed为true时候,就给cookie的值加上签名,用点相连
}
arr.push(`${key}=${value}; ${opts.join('; ')}`);
res.setHeader('Set-Cookie',arr); // 最终的设置Cookie
}
// 通过服务端去写入cookie
if (req.url == '/read') {
// 读取cookie
res.end(req.getCookie('name',{signed:true}) || 'empty');
} else if (req.url == '/write') {
// 设置cookie
// 当给浏览器设置cookie时 可以增加签名 根据数据内容创建一个唯一的签名 MD5
// 加盐算法 根据内容 和秘钥算出一个签名 (不能反解) 相同的秘钥签名的结果是相同
res.setCookie('name','MrLi',{ // 1.可以对设置一个字段 MrLi.sign
httpOnly:true,
maxAge:200,
signed:true
});
res.setCookie('age','100');
res.end('write Ok')
} else {
res.end('Not Found')
}
}).listen(3000);