HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。html
Cookies特性前端
当服务器收到HTTP请求时,服务器端经过在响应头中添加Set-Cookie
选项设置cookie
。浏览器收到响应后一般会自动保存Cookie
,以后每一次对该服务器的请求中都会经过Cookie
请求头将Cookie
信息发送给服务器。node
Set-Cookie: <cookie名>=<cookie值>web
服务器使用Set-Cookie
响应头向用户代理(浏览器)发送Cookle信息算法
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry后端
对该服务器发起的每一次请求浏览器都会自动将以前保存的Cookie信息经过Cookie
请求头发给目标服务器api
浏览器关闭会自动删除,仅在会话期有效。浏览器
持久性Cookie的生命周期取决于过时时间(Expires)或有效期(Max-Age)指定的时间周期安全
设置cookie值得有效时间是2021年5月6日20点53分服务器
Set-Cookie: id=a3fWa; Expires=Thu, 06 May 2021 12:53:27 GMT;
复制代码
Expires的值设置的是GMT的时间格式,比当前时间少了8小时。
以当前时间为例,转换以下:
let time = new Date()
time.toGMTString()
//"Thu, 06 May 2021 12:53:27 GMT"
复制代码
提示:当Cookie的过时时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
若是Cookie没有设置expires属性值,那么 cookie 的生命周期只是在当前的会话中, 关闭浏览器意味着此次会话的结束,此时 cookie 随之失效。
Set-Cookie: id=a3fWa; Expires=Thu, 05 May 2021 12:53:27 GMT;
复制代码
expires设置一个过去的时间点,那么这个cookie 会被当即删掉(失效)
标记为 Secure 的 Cookie 只应经过被 HTTPS 协议加密过的请求发送给服务端
JavaScript Document.cookie API 没法访问带有 HttpOnly 属性的cookie;此类 Cookie 仅做用于服务器。
示例:
Set-Cookie: id=abed; Expires=Thu, 06 May 2021 12:53:27 GMT; Secure; HttpOnly
复制代码
Domain
和Path
标识定义了Cookie的做用于:即容许Cookie应该发送给那些URL。
Domain 指定了哪些域名(主机)能够保存 Cookie。若是不指定,默认为 origin,不包含子域名。若是指定了Domain,则通常包含子域名。
例如,若是设置 Domain=example.com,则 Cookie 也包含在子域名中(如developer.example.com)。
Path
标识指定了域名(主机)下的哪些路径能够保存 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 做为路径分隔符,子路径也会被匹配。
例如:设置Path=/list
,如下路径都会给匹配:
SameSite Cookie 容许服务器要求某个 cookie 在跨站请求时不会被发送
复制代码
示例:
Set-Cookie: key=value; SameSite=Strict
SameSite有如下三种值:
浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写。
浏览器将只在访问相同站点时发送 cookie。
与 Strict 相似,但用户从外部站点导航至URL时(例如经过连接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送。
...
根据业务场景,灵活选用
示例:
userId=1; _ga=GA1.1.245187848.1620008273//userId->uid
userId=142389; _ga=GA1.1.245187848.1620008273//userId->工号
...
复制代码
以上已经登陆的用户cookie信息,攻击者很容易推算出其余用户的cookie,从而冒用其余用户身份。
攻击者经过如上保存的cookie,很容易猜到userId保存的规律,采用某种手段更改已登陆的cookie信息,就能够冒用其余用户的身份。
此时咱们会想到给userId签名,可是签名后的userId后端接口也没法逆向签名,并且签名依然能够被更改,接口又该怎么识别尼?
正确的作法是:
签名和真实的userId同时都透传给接口,接口拿到签名和userId后经过和前端一样的签名算法计算签名结果和原签名比较,最终确认用户信息的准确性。
复制代码
// crypt.js
var crypt ={};
const key ='fs)3!dsg/8%';//key是本身随便定义,越随意约安全
crypt.cryptUerId = function(userId){
var crypto = require('crypto');
var sign = crypto.createHash('sha256',key);
sign.update(userId+'');
return sign.digest('hex')
}
module.exports = crypt;
复制代码
// login.js
const crypt =require('../tools/crypt')
let sign = crypt.cryptUerId(user.id)
//这里须要透传userId和sign,由于接口没法将sign逆向签名成原userId,只能在利用一样的签名算法对userId再次进行签名,校验两次签名值
ctx.cookies.set('userId', user.id,{ httpOnly: false, sameSsecure: false });
ctx.cookies.set('sign', sign,{ httpOnly: false, sameSsecure: false });
复制代码
// addComment.js
const crypt = require('../tools/crypt')
...
let userId = ctx.cookies.get('userId');
let sign=ctx.cookies.get('sign')
if(sign!==crypt.cryptUerId(userId)){
throw new Error('有人在瞎搞!')
}
复制代码
用户在访问目标主机后,主机接口根据某种加密算法生成sessionId,并保存在cookie中,之后的每一次请求中都会携带sessionId做为用户身份识别标志。
//session.js
var session = {};
var cache = {};
session.set = async function (sessionId, obj) {
var sessionId = Math.random();//随机生成sessionId
if(!cache[sessionId]){
cache[sessionId]={}
}
cache[sessionId].content =obj;
return sessionId;
}
session.get = function (sessionId) {
return cache[sessionId]&& cache[sessionId].content
}
module.exports = session;
复制代码
// login.js
const session = require('../tools/session')
...
var sessionId = session.set(user.id,{userId:user.id});
ctx.cookies.set('sessionId',sessionId,{ httpOnly: false, sameSite: "Lax", secure: false })
复制代码
// addComment.js
...
var sessionId = ctx.cookies.get('sessionId');
var sessionObj = session.get(sessionId);
if(!sessionObj || !sessionObj.userId){
throw new Error('session不存在')
}
...
复制代码
MDN_HTTP cookies
node_crypto-加密
五分钟带你了解啥是JWT
cookies做为安全防护的重点,值得咱们去深刻研究。 cookie的做用固然也不止笔者介绍的这些。 本文记录的是笔者在开发过程当中遇到问题引起的思考和探索。可供有相似问题的读者参考。 其余安全方面的文章笔者会持续更新,欢迎各位读者提出意见和建议。