不少人都已经看过 Session 和 Cookie 相关的入门文章,却只限于纸上谈兵,不懂得实际运用,本文从最小项目入手,结合前端跨域、HTTP 等知识点,作一次深刻实践javascript
在用户访问网站时,咱们常常须要记录一些信息,好比html
这时候咱们能够借助 Cookie,如下来自 MDN 的官方解释前端
HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。一般,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登陆状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。java
Session 中文意思名为“会话”,是一种解决方案,表明客户端和服务端的一次通讯过程,在这个过程当中若是客户端须要记录数据,服务端会暂时把数据挂载到 session 对象上,当请求结束响应时,将 session 中挂载的数据持久化到客户端的 cookie上,清空 session,关闭会话web
Cookie 能够看作一个信息容器,借助浏览器的环境对服务端的数据进行持久化存储,随后每次都会在 HTTP 请求头中携带并发送至服务端,这样服务端就能够辨识请求的来源数据库
下面,咱们借助 Koa 框架搭建后端服务,来走一遍具体流程,新建一个koa-demo
项目npm
mkdir koa-demo && cd koa-demo
npm init -y
cnpm i koa --save
touch app.js index.html
复制代码
写入如下代码编程
// app.js
const Koa = require('koa');
const app = new Koa();
app.use((ctx) => {
ctx.body = 'hello, Koa';
});
const PORT = '8080';
app.listen(PORT, () => {
console.log(`server is running at http://localhost:${PORT}`);
});
复制代码
运行并访问localhost:8080
,就能够看到访问成功!json
cnpm i koa-router koa-body --save
复制代码
// app.js
const Koa = require('koa');
const Router = require('koa-router'); // 实现Koa的路由机制
const koaBody = require('koa-body'); // 对请求体中的数据作格式化处理
const app = new Koa();
const router = new Router();
app.use(router.routes()).use(router.allowedMethods());
app.use(koaBody());
router.post('/login', (ctx) => {
const { usr } = ctx.request.body;
ctx.body = usr;
});
const PORT = '8080';
app.listen(PORT, () => {
console.log(`server is running at http://localhost:${PORT}`);
});
复制代码
在index.html中添加如下代码后端
<!-- index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<button id="btn">send request</button>
</body>
<script> const btn = document.getElementById('btn'); const data = { usr: 'b2d1', psd: '123' }; const request = () => { return fetch('http://localhost:8080/login', { body: JSON.stringify(data), method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8' } }); }; const sendRequest = async () => { const res = await request(); // 返回一个 ReadableStream 对象 return await res.text(); // 因为后端返回一段文本数据,利用text()来获取数据,相似的还有json(),blob() }; btn.addEventListener('click', async () => { const msg = await sendRequest(); console.log(msg); }); </script>
</html>
复制代码
注意,咱们采用 Fetch API
替代了 XMLHttpRequest API
,Fetch 方法提供了一种简单,合理的方式来跨网络异步获取资源。Fetch 还提供了单个逻辑位置来定义其余 HTTP 相关概念,例如 CORS 和 HTTP 的扩展。
为了最大程度上还原开发时的场景,咱们cnpm i serve --save
,它可使本地静态文件成为在浏览器端口上运行的静态站点
img
标签的
src
属性,嵌入
script
脚本)
CORS(跨域资源共享)是一种网络浏览器的技术规范,为web服务器跨域访问控制提供了安全的跨域数据传输。
根据控制台的提示,咱们须要在服务器的响应头中加入Access-Control-Allow-Origin:whiteList
,这个whiteList
能够是*
或者http://localhost:5000
,咱们能够借助koa-cors
来快速设置CORS
cnpm i koa-cors
复制代码
// app.js
const cors = require('koa-cors');
// ...
app.use(cors());
app.use(koaBody());
app.use(router.routes()).use(router.allowedMethods());
// ...
复制代码
这里 app.use
的顺序十分重要,由于 Koa
自己结构简单,核心代码只有一两百行,包括挂载 Request
和 Response
到 Context
上,Compose
实现中间件(Middleware
)依次调用,即洋葱模型,每一个请求都会通过全部中间件的过滤
因此,咱们能够利用丰富的中间件使自己短小精悍的 Koa 应用构建成为大型的 Web 应用
话很少说,继续点击按钮
/login
请求,这是怎么回事呢?
符合如下条件的就是简单请求,反之就是非简单请求
而咱们在fetch配置中,指定了Content-Type
,故会发起一次预检请求,来请示服务端是否执行客户端真正的请求。
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
复制代码
而 koa-cors 已经为咱们考虑周到,在背后作了这几件事:
cnpm i koa-session --save
复制代码
koa-session 是一个高度封装了 Cookie 和 Session 操做的 NPM 包
const session = require('koa-session');
app.keys = ['some secret hurr']; // 做为cookies签名时的秘钥
const CONFIG = {
key: 'koa:sess', // cookie的键名
maxAge: 86400000, // 过时时间,这里为一天的期限
overwrite: true, // 是否覆盖cookie
httpOnly: true, // 是否JS没法获取cookie
signed: true, // 是否生成cookie的签名,防止浏览器暴力篡改
encode: (json) => JSON.stringify(json), // 自定义cookie编码函数
decode: (str) => JSON.parse(str) // 自定义cookie解码函数
};
// 再次强调,app.use(fn)的顺序很重要
app.use(session(CONFIG, app));
app.use(cors());
app.use(koaBody());
app.use(router.routes()).use(router.allowedMethods());
...
复制代码
接下来,改造一下登陆接口
// app.js
router.post('/login', (ctx) => {
const { usr } = ctx.request.body;
const logged = ctx.session.usr || false;
if (!logged) {
ctx.session.usr = usr;
ctx.body = 'welcome, you are first login';
} else {
ctx.body = `hi, ${ctx.session.usr}, you haved logined`;
}
});
复制代码
咱们满怀期待的点下按钮,成功啦!
默认状况下,fetch 不会从服务端发送或接收任何 cookies, 若是站点依赖于用户 session,则会致使未经认证的请求(要发送 cookies,必须设置 credentials 选项)。
真相大白,咱们须要手动设置credentials
属性的值为include
,才能在当前域名内自动发送 cookie,回到 index.html,修改request
函数
return fetch('http://localhost:8080/login', {
credentials: 'include',
...
});
复制代码
来,再次点击按钮,出现以下错误
'Access-Control-Allow-Credentials':'true'
的 Header,而 koa-cors 已经内置了相关 API,只需修改一下
app.use(cors({ credentials: true }));
最终咱们点击按钮,第一次首次登陆,没有问题
Set-Cookie
的 Header, 多是由于跨域的缘由,我在 Chrome 的请求响应体里死活找不到,用了 Firefox 就能够看到了
继续点击,服务器已经记住了咱们登陆状态
就自动登陆而言,大体流程以下图所示
CONFIG
中快速配置
Cookie的名称
Cookie的值,常为一段通过JSON.stringify()
处理后的字符串
分别指 Cookie 的一个特定的过时时间和有效期
可是事实并非如此,我在 Chrome 和 Firefox 中尝试会话 Cookie,先修改 koa-session 的maxAge
属性为session
const CONFIG = {
maxAge: 'session',
}
复制代码
点击按钮,能够看到 Expires
属性被设置为N/A
指定了哪些主机域名能够访问 Cookie,Request Body
中的Host
字段表明了主机域名,若是设置 Domain = .b2d1.top
,那么 m.b2d1.top、b2d1.top
也包含在 Cookie 的访问范围内,实现多网站共享 Cookie
指定了主机域名下的哪些路径能够访问Cookie,如设置/docs
,则如下http://Domain/docs、http://Domain/docs/web/、http://Domain/docs/web/HTTP
路径均可访问 Cookie,其余路径获取不到 Cookie
如设置为true,则不能经过document.cookie
来访问此 Cookie
Cookie 的大小
如设置为true
,则只应经过被 HTTPS 协议加密过的请求发送给服务端 - 当咱们在http协议中,试图接受设置 Secure 为 true 的 Cookie 时,服务端会报错, Error: Cannot send secure cookie over unencrypted connection
至此,咱们的 koa-demo 已经实现了最基本的登陆接口,并借助 Seesion 和 Cookie存储用户登陆状态的功能,可谓小而美。
通过上述的 demo 演示,其实核心就是一句话
Session 是一种服务端接受会话信息的解决方案,Cookie 是客户端实现的一个信息容器
那么,咱们是否能够把信息存储到其余地方,答案是固然能够,理论上,能够存储到任何媒介(Cookie,数据库,系统文件)。出于安全考虑,咱们能够在 Cookie 中保存 session 的 externalKey,将信息主体保存到数据库中,经过 externalKey 来映射数据库中的信息主体。
externalKey 事实上是 session 数据的索引,此时相比于直接把 session 存在 cookie 来讲多了一层,cookie 里面存的不是 session 而是找到 session 的钥匙。固然咱们保存的时候就要作两个工做,一是将 session 存入数据库,另外一个是将 session 对应的 key 即(externalKey)写入到 cookie
koa-session 为咱们提供了 store 接口并提供三个方法:get、set、destroy,来实现自定义的存储机制
// app.js
let store = {
storage: {},
get(key, maxAge) {
return this.storage[key];
},
set(key, sess, maxAge) {
this.storage[key] = sess;
},
destroy(key) {
delete this.storage[key];
}
};
const CONFIG = {
...,
store
};
router.post('/login', (ctx) => {
...
console.log(store.storage);
});
复制代码
清除Cookie,从新发起请求,能够看到此时,Cookie 的 Value 为一段随机生成的 Key
本文涉及的知识点较多,建议本身手把手敲出 koa-demo,针对 koa-session,还有不少值得探讨的地方
我会专门写一篇 koa-session 源码解析的文章,提升对 Koa 框架的理解,JS 编程思想的提升,如何从底层处理 Session 和 Cookie
好比 Cookie 的处理,你们能够先睹为快