cookie-session-fetch

背景

最近一定是太“闲”了,遇到一个问题非要深究下去,最近在做koa-weixin的时候遇到几个问题,当然也是因为之前学习不够深入造成的;

  1. 默认使用koa-generic-sessionkoa-redis配合使用完成session机制,可是存在一个问题,默认cookie保存时间为一天,我认为关闭浏览器cookie就会被销毁,结果第二天打开网站发现能够登陆;
  2. 修改cookie保存时间可行,但是却始终无法使得关闭浏览器cookie失效;
  3. 使用fetch异步发送数据,无法发送cookie;

cookie及session原理

保持一个会话的方式是利用session存储在服务器,常见的存储方式是存储在内存、文件、redis和数据库,不管使用哪种方式,都需要产生一个session,并将session id存储到cookie,通过将cookie发送给客户端来保持连接,在每次客户端发送请求的时候,都会带上cookie的相关信息,其中就包括session id,在服务器端通过session id来查询session的相关信息

下面内容转载自 虫师 :

我们都知道HTTP协议本身是无状态的,客户只需要简单的向服务器来发送请求下载某些文件,客户端向服务器端发送的每次请求都是独立的。对于当前的web应用,HTTP的“无状态”,导致许多应用都不得不花费大量的精力来记录用户的操作步骤。就像我们上面介绍的第一种情况,银行职员要花费大量的精力来记忆每一位用户的存/取款记录。

程序员很快发现,如果能够提供一些按需生成的动太信息,会使web的交互能力大大增强。程序员一方面在HTML中添加表单、脚本、DOM等客户端行为,来增加web应用与客户端的交互性。另一方面在服务器端测出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议添加了文件上载、cookie 等特性。那cookie的原理与我们上面介绍的使用存折记录用户应为的方式是一样一样的。

通过前面的例子我们已经发现,通过cookie的方式存储信息,可能会存在一点定的安全性,因为所有的信息都是写在客户端的,客户可能会对这些信息进行修改或清除。然后就又出现session的方式用于保存用户行为,这种方式的原理与前面介绍银行卡的方式是一样的。

具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

cookie机制。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。

cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。

session机制

session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识————称为session id,如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。

经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。

Jsessionid

Jsessionid只是tomcat的对sessionid的叫法,其实就是sessionid;在其它的容器也许就不叫jsessionid了。

关闭浏览器cookie清除

cookie 参数:

  • path:表示 cookie 影响到的路径,匹配该路径才发送这个 cookie。
  • expires 和 maxAge:告诉浏览器这个 cookie 什么时候过期,expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,会产生 session cookie,session cookie 是 transient 的,当用户关闭浏览器时,就被清除。一般用来保存 session 的 session_id。
  • secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
  • httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。一般情况下都应该设置这个为 true,这样可以避免被 xss 攻击拿到 cookie。

所以为了使得关闭浏览器清除cookie应该不设置expires/max-age,深层的原理,当设置expires/max-age后,浏览器默认会把cookie存放到硬盘里面,所以即使关闭浏览器,甚至关闭电脑后,下次打开浏览器仍然会保存着cookie的信息。

可是查看koa-generic-session的源码可以发现:

1
2
3
4
5
6
7
const defaultCookie = {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: 24 * 60 * 60 * 1000 //one day in ms
};

maxAge默认时间为1天,所以需要修改源码为:

1
2
3
4
5
6
7
const defaultCookie = {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: null //one day in ms
};

如果有需要可以在index.js中设置

1
2
3
4
5
6
7
8
9
10
app.use(session({
store: redisStore(),
cookie: {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: null //one hour in ms
}
}));

好了大功告成

fetch中cookie问题

理论上所以http请求都应该带上cookie,可是fetch比较坑

Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: ‘include’})

ajax版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var key = document.getElementById('title').value;
var val = document.getElementById('lab-create-input').value;
if (key.trim() === '' || val.trim() === '') {
alert('关键字或者回复,不能为空');
} else {
var data = {
key: key,
val: val
};
var xhr = new XMLHttpRequest();
xhr.open('post', '/weixin/addK', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var result,
status = xhr.status;
if ((status >= 200 && status < 300) || status === 304) {
result = xhr.responseText;
console.log(result);
} else {
console.log("ERR", xhr.status);
}
}
};
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
}

fetch版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var key = document.getElementById('title').value;
var val = document.getElementById('lab-create-input').value;
if (key.trim() === '' || val.trim() === '') {
alert('关键字或者回复,不能为空');
} else {
var data = {
key: key,
val: val
};
data = JSON.stringify(data);
fetch('/weixin/addK', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: data
});
}

fetch坑

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: ‘include’})
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  • IE8 IE9的XHR不支持CORS跨域,推荐使用fetch-jsonp

参考

简书 KeKeMars