当今流行的网页开发模式是前后端分离的开发模式,即后端只负责API的开发,将其暴露给前端,前端负责页面逻辑,利用后端的API完成网页的构建。而在开发大型应用时,session又是常用的用来保存信息的技术。
这篇文章在后端使用Node.js的Express框架,前端使用Vue.js,用axios进行异步请求,介绍如何在前后端分离的项目中使用Session。
注意:前后端分离可以有两种实践方案:
这篇文章采用第一种方案。
Session存储信息和Cookie存储信息类似,不过传统的Cookie存储方法是将信息存储在客户端(浏览器),而Session是将信息存储在服务器上。
Session也是依赖Cookie的!
在浏览器中,一个Session Key
被存储在Cookie中,这是一个应当被精心设计过的字符串,它应当独一无二,而且还要避免被暴力破解。这个Session Key
是用户身份的识别信息,一个Session Key
对应一个用户。
在服务器中存储着很多的Session,中文成为会话,它们就像服务器和客户端的会话一样,一种常见的实现就是哈希表,它的键是Session Key
,值就是存储的信息,服务器根据用户的Session Key
决定给用户访问特定信息的权限。
当用户发送请求到服务器时,Session Key
在Cookie字段中也被发送给服务器,这时服务器根据Session Key
识别用户,验证通过后就会将该Session Key
对应的数据返回给用户。
例如当一个用户登录时,验证成功后一个Session Key
会被保存在浏览器内,当用户关闭浏览器一段时间后再打开,在请求网页时,会将这个Session Key
发给服务器,服务器收到后发现用户已经登录,且登录还没有过期,就会直接让用户登录,不用再进行校验。
后端采用Node.js的Express框架,首先要安装最新版本的Node,这里不赘述了,然后要安装Express和Session,这是两个库,要分别安装:
npm install --save express
npm install --save express-session
新建一个JS文件,命名为app.js
,这是我们这个后端的主入口,建立后文件结构应该如下:
在app.js
中添加如下内容:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'billy',
resave: false,
saveUninitialized: false,
}));
app.listen(3000);
express
和express-session
两个库。secret
选项是生成Session Key
的哈希函数使用的密钥,相当于在生成Session Key
时哈希函数的输入值多了一些内容,有助于避免结果被破解。resave
选项表示是否重复保存,有时用户访问Session后并没有修改Session的内容,这时候如果把这个选项设置成false
可以避免在Session未被修改时重复保存。saveUninitialized
表示是否保存未初始化的Session,有时用户访问页面时并没有创建Session,比如用户进了主页但是并没有登录或注册,这时候Session是没有初始化的,把这个选项设置为false
可以避免由于用户并未使用Session而在服务器保存多余的Session。3000
端口,表明服务器运行在本机的3000
端口上。Express中的Session默认将Session保存在内存中,也就是说,如果后端服务器重启,所有的Session数据都将丢失,在实际开发中,如果你需要长期保存Session的数据,请务必使用数据库来保存Session。
让我们来配置一个路由,我们在Session中保存一个用户最早访问网站的时间,如果用户是第一次访问网站,我们就保存下当前的时间并返回这个时间,如果用户此前已经访问过了我们的网站,就返回用户最早访问这个网站的时间。
在express中,session被保存在req.session
中,这是一个JavaScript对象,在服务端可以对这个对象进行增删改查操作,上文说过了,不同的用户的Session也是不同的,所以req.session
根据用户不同访问到的是不同的对象,完成后我们的服务器端的app.js
文件如下:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'billy',
resave: false,
saveUninitialized: false,
}));
app.get('/time', (req, res) => {
if (!req.session.time) {
req.session.time = Date();
}
res.send(req.session.time);
});
app.listen(3000);
启动后端服务器:
node app.js
可以在浏览器中访问localhost:3000/time
看看效果如何:
刷新后或者关闭浏览器重新打开都不会改变这个时间,因为它已经被保存在Session中了,根据我们的逻辑它是不会改变的。
我们用Vue CLI 快速搭建一个前端页面,如果你想了解一下Vue CLI如何快速搭建项目,可以参考我的另外一篇文章:Vue CLI 3 快速搭建项目。
我们使用axios
这个库进行异步请求的发送,这是一个基于Promise
的异步库,如果你想了解更多关于axios
的内容,请移步axios 中文说明。
安装axios
:
npm install --save axios
为了方便我把全部的内容都放在App.vue
这个文件中了,文件内容如下:
<template>
<div id="app">
<h1>Time: {{time}}h1>
div>
template>
<script>
import axios from 'axios';
export default {
name: 'app',
data() {
return {
time: null,
}
},
created() {
axios.get('http://localhost:3000/time')
.then((res) => {
this.time = res.data;
});
}
};
script>
这个页面的逻辑就是当页面加载时,异步从服务器获取最早访问的时间,然后将这个时间字符串赋值给data
中的time
属性,在页面上将这个字符串渲染出来。
以上页面逻辑应该是毫无问题的,但是启动前端项目打开页面后产生如下结果:
这里涉及到了CORS,中文全称为跨域资源共享的问题,是一个浏览器端的保护措施。浏览器为了保护用户的安全,在正常情况下会禁止从其他域读取资源。什么情况下浏览器会认为是不同的域呢?有的人可能在这里会产生疑惑,明明都是localhost
域,为什么还会有跨域请求的问题呢?
其实,只有协议、域名和端口都相同时,浏览器才会认为是同源的,这里协议和域名是相同的,但是端口不同(后端是3000
端口,前端是8080
端口),所以浏览器仍然认为这是一个跨域请求,出于安全考虑禁止了它。
为了允许跨域请求,需要在后端添加响应头,字段名为Access-Control-Allow-Origin
,我们在后端用一个中间件来进行配置,在app.js
中添加如下内容:
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', '*');
next();
});
这里的*
就是允许跨域访问,而且允许的域不限。
虽然数据能正常显示,但是新问题又出现了,正常来说页面刷新以后时间应当是不变的,因为服务器会一直返回同一个Session中保存的数据,但是这里刷新后时间会不停变化,也就是说,每次访问接口,服务器都会新建一个Session,这不是我们想要的结果,打开浏览器终端,切到Network
标签查看一下请求内容如下:
我们在这个请求中可以发现,没有任何关于Cookie的内容,也就是说,Session Key
没有传给服务器,在这种情况下,服务器每次都以为是一个新用户连接进来了,所以返回了新的Session中的内容,为了解决这个问题,我们要让axios
把Cookie传给服务器,在App.vue
中引入axios
的代码下添加如下代码:
axios.defaults.withCredentials = true;
添加后打开网页,又无法正常显示了,打开终端看一下报错:
由于设置完毕后,请求中包含了如Cookie等私密内容,所以允许跨域的设置中不能将允许的域设置为全部(*
),而且后端还要特别指明允许私密内容的发送,我们这里要在后端进行一些修改:
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', 'http://localhost:8080');
res.set('Access-Control-Allow-Credentials', 'true');
next();
});
修改完成后内容可以正常显示了,且刷新后不会再变化,说明访问的是同一个Session:
最终的请求也已经包含了Session Key
:
最终前端代码:
<template>
<div id="app">
<h1>Time: {{time}}h1>
div>
template>
<script>
import axios from 'axios';
axios.defaults.withCredentials = true;
export default {
name: 'app',
data() {
return {
time: null,
}
},
created() {
axios.get('http://localhost:3000/time')
.then((res) => {
this.time = res.data;
});
}
};
script>
<style>
style>
最终后端代码:
const express = require('express');
const session = require('express-session');
const app = express();
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', 'http://localhost:8080');
res.set('Access-Control-Allow-Credentials', 'true');
next();
});
app.use(session({
secret: 'billy',
resave: false,
saveUninitialized: false,
}));
app.get('/time', (req, res) => {
if (!req.session.time) {
req.session.time = Date();
}
res.send(req.session.time);
});
app.listen(3000);
Access-Control-Allow-Origin
设置,如果要传Session Key
的话还要再允许私密信息的传输,通过Access-Control-Allow-Credentials
设置。axios
默认是不会传输Cookie等信息的,要手动配置才可以,通过axios.defaults.withCredentials = true
来设置。本文中的例子我已经放到GitHub上,请点击访问前端代码或后端代码来查看或下载它们。