前后端分离开发时,我们的前端请求是跨域请求,会造成session和cookie失效的问题。
在阅读多种解决方法后,我选择了使用redis来实现session的解决方案,确保前端使用跨域请求的情况下,后端可以维持用户session.
起因
为什么选择redis来实现跨域下的session呢?
我阅读了多种跨域session丢失的解决办法,但都没有生效,于是最后选择了redis才解决了这个问题。
前后端添加credential后,浏览器无法自动加载第三方cookie, 服务端的session和cookie仍然丢失。
前端Fetch跨域请求
const data = {key:"value"};// 参数
const myHeaders = new Headers();
myHeaders.append('Access-Control-Allow-Origin', 'http://localhost:3000/');
// 设置Access-Control-Allow-Credentials,跨域请求带授权
myHeaders.append('Access-Control-Allow-Credentials', 'true');
// 设置mode为cors,进行跨域请求
myHeaders.append('mode', 'cors');
// 设置Content-Type, 参数格式
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded')
var urlencoded = new URLSearchParams()
for (let key in data) {
urlencoded.append(key, data[key])
}
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: urlencoded,
redirect: 'follow',
// 设置为include确保跨域请求保持cookie
credentials: 'include',
}
fetch(api, requestOptions).then(response=>response.json());
Egg跨域配置
// config.default.js
'use strict'
const path = require('path')
module.exports = appInfo => {
const config = {
mode: 'file',
errorHandler: {
match: '/'
},
}
// 设置security里的csrf关闭,允许跨域请求
config.security = {
domainWhiteList: ['*'],
csrf: {
enable: false
}
}
// 设置cors插件配置, origin为前端的来源(带credentials无法设置为*)
// 设置credentials为true,返回的Header带有Access-Control-Allow-Credentials为true
config.cors = {
origin: 'http://localhost:3000',
credentials: true,
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
}
// 这里设置egg自带的session,但跨域请求仍然丢失session
config.session = {
key: 'WheelFit',
maxAge: 30 * 24 * 3600 * 1000, // 30 days
httpOnly: true,
encrypt: true,
renew: true
}
// 自定义中间件, sessionToken为鉴权中间件,在收到请求后获取session里存储的token
config.middleware = ['sessionToken', 'errorHandler']
return {
...config,
}
}
// plugin.js
'use strict';
// 添加cors插件用于回复跨域请求
// npm i --save egg-cors
module.exports = {
cors: {
enable: true,
package: 'egg-cors',
}
};
这个配置是在其他的解决跨域session丢失问题的文章中找到的,理论上添加credientals即可保持session,但我这里并没有生效,跨域请求仍然会丢失session,并且给response设置的cookie也不能被浏览器获取到。但是使用postman,发送非跨域请求,是可以记录的session的。
无奈我只能选择使用redis维持跨域请求的session
Redis 配置
前端Fetch请求仍然保持不变(虽然credential已经不需要设置)
后端需要新增egg-redis插件
// plugin.js
'use strict';
// 添加cors插件用于回复跨域请求
// npm i --save egg-cors
// npm i --save egg-redis
module.exports = {
cors: {
enable: true,
package: 'egg-cors',
},
redis: {
enable: true,
package: 'egg-redis',
},
};
// config.default.js
config.redis = {
client: {
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
password: '',
db: 0
}
}
使用redis设置值
// login.js
async login(){
const {ctx} = this;
// login operation
// login successfully
const token = generateToken();
ctx.app.redis.set(`token${userid}`, token);
return (ctx.body={success:true, userid: userid})
}
// check session
async session_token() {
const {ctx} = this;
const {userid, token} = ctx.request.body;
const redis_token = ctx.app.redis.get(`token${userid}`);
if(redis_token === token) {
return (ctx.body={success: true});
}
return (ctx.body={success: false});
}
这样我们在服务端使用redis为跨域请求设置缓存状态,我们还可以设置定时任务,删除该状态。