主题
session + Cookie鉴权机制
一. cookie鉴权的缺点(安全性)
由于单纯的cookie鉴权,服务器都把信息放进cookie,但是cookie是存储于客户端(浏览器),是明文的,即使服务端设置了cookie的httpOnly属性,也就是cookie是只读的,但是仅仅只是代码层面的只读,若是通过浏览器的控制台手动修改只读的cookie,依然能够成功,也就是说,客户端可以篡改,这就会对服务端造成安全漏洞,这才出现了session,session是服务端的一个存储空间,一般存在于内存,也可以用redis,mongodb等存储,鉴权原理见下文。
二. session鉴权原理
原理和流程:
- 当用户登录成功后,后端会生成一个session对象,并产生一个sessionId标识这个对象。然后会把这个sessionId返回给前端,存储在前端浏览器cookie中,从此这个sessionId就代表用户的这个身份了。
- 当用户在登录后要发起任何操作,浏览器都会自动的带上这个sessionId向服务端表明自己的身份,服务收到这个sessionId后,服务端会去查找到对应的session对象,如果找到了就表示身份验证通过,否在就不通过。
- 然后如果用户不登录直接发起查询操作去请求数据,那么由于没有后端颁发的sessionId,直接就会被后端拒绝验证。
1. 用原生nodejs
演示session
原理
js
const http = require("http");
const uuid = require('uuid');
const queryString = require('querystring');
let sessionName = "Mr.Li"; // cookie中sessionID的键名
let session = {}; // 模拟的session
http.createServer((req, res) => {
if (req.url === "/visit") {
let cookieObj = queryString.parse(req.headers.cookie, '; ', '='); // 解析cookie字符串,得到一个cookies对象
let s = null
// 若请求里面携带了cookie,且里面的sessionID在服务端有,则让count++
if(cookieObj && (s = cookieObj[sessionName]) && session[s]) {
res.setHeader('content-type','application/json;charset=utf8');
res.end(`您第${session[s].count ++}次访问我`)
}else { // 若服务端没有session,则生成一个
let sessionId = uuid.v4(); // 随机生成一个sessionID
session[sessionId] = {count:0}; // session中,sessionID内对应的对象是内容
res.setHeader('set-cookie',[`${sessionName}=${sessionId}`]) // 给浏览器种植cookie
res.setHeader('content-type','application/json;charset=utf8');
res.end(`您第${session[sessionId].count}次访问我`)
}
} else {
res.end("Not Fond");
}
})
.listen(3000);
效果展示
由于session是存属于服务端内存里面,所以服务端重启后,内存中的session就会丢失。 因此,可以将session存储于redis,一方面,方便管理,可以持久化,另一方面,redis也可以设置过期时间,(redis内的session与浏览器的cookie设置相同过期时间)当cookie过期后,浏览器的cookie会自动删除,服务端的session也自动删除了,不会因cookie过期而导致无用的session不断增多。
三. express中用session鉴权
1 实现流程
- nodejs服务端安装和使用cookie和session中间件(为了实现处理session和token的能力)
- 当用户登录成功时,后端向req.session中写入数据
- 当后端返回数据给前端时,会自动生成sessionid,标识当前登录了的这个用户
- 登陆之后,前端的每一次请求都会自动带上sessionid(因为请求会自动携带cookie)
- 后端收到前端请求后通过sessionid自动找到session,然后会通过session中存储的用户基本信息来判断用户是否有效
2 代码实现
2.1 安装依赖
为了处理cookie与session,需要安装相关模块
sh
npm install express-session -S
npm install cookie-parser -S # 【这个模块在express项目中默认安装了的,不需要安】
2.2 express逻辑代码
1.先引入
由于express项目默认cookie-parser是引入了的,所以我们只需要引入express-session模块即可
js
var cookieParser = require('cookie-parser');
var session = require('express-session');
2.后端cookie相关处理
如果前后端没有分离进行开发的话,cookie在前后端的传递时非常简单的。
但是自从前后端分离后,cookie的处理就稍微要麻烦点了。 此时,前后端分别都要处理下,这里先说下后端的处理行为,后端在跨域时需要做出如下的处理
js
//使用中间件形式实现跨域访问
app.use("*", function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
//允许的header类型
res.header("Access-Control-Allow-Headers", "content-type");
//接收ajax请求手动提交的cookie信息
res.header("Access-Control-Allow-Credentials", true);
//跨域允许的请求方式
res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
if (req.method == 'OPTIONS')
res.sendStatus(200); //让options尝试请求快速结束
else
next();
});
3.在跨域代码的后面使用这个两个模块
只有写了下面的代码,express后端才能解析处理cookie和session,否在默认情况下是处理不了的
js
app.use(cookieParser()); // 此行代码express中默认含有,只写下面的代码即可
app.use(session({
name: "sessionId",
secret: "mrli", // 用来对session id相关的cookie进行签名
saveUninitialized: false, // 是否自动保存未初始化的会话,建议false
resave: false, // 是否每次都重新保存会话,建议false
})
);
2.3 登录成功后,后端创建session对象,并返回sessionId到前端
当用户在登录成功时,我们需要将用户的基本信息写入session中,如用户名。然后当我们执行了此操作后,响应对象会自动的向前端发送sessionId,存入浏览器的cookie中。
js
//将用户名存入sesson
req.session.user = parobj.username;
2.4 session鉴权验证--后端处理过程
后端使用中间件在所有请求的前面,实现对请求的判断:
- 如果是登录请求,说明是最初始的状态,让其正常登录,从而获取sessionId
- 如果非登录请求,则必须验证sessionId, 如果没有或者通过sessionId无法找到对应的session,那么就拒绝操作。反之这运行操作。
js
// 我们可以把这段代码写在正式进入路由模块之前
app.use(function (req, res, next) {
console.log("req.url", req.url)
console.log("Cookies: ", req.cookies);
console.log("------------");
if (req.url == "/v1/users/login") { //如果是登录请求,直接通过验证
next();
}
else { //表示非登录请求,需要验证session
if (req.session.user) {
next(); //session验证通过
}
else { //session验证失败
res.send({ succ: false, msg: "session无效,请重新登录" })
}
}
});
2.5 session鉴权验证--前端处理过程
前端其实没什么好处理的,只是我们后端在返回的sessionId是存储在前端的浏览器的cookie中的,而在前后端的分离开发模式下,前端如果想要自动的传入cookie到后端,需要在请求时加上一个字段,如下:
js
credentials: 'include',
提示:如果你不是用的fetch,而是用的ajax或者axios等发其的请求,也是类似字段,当然并不是同一个单词,有细微差别。
axios
js
axios.defaults.withCredentials = true; // 是否允许请求携带Cookie
四. koa中使用session鉴权
1. 安装依赖
sh
npm i koa-session -S
2. 设置一个keys,用于加密cookie
js
const app = new koa()
app.keys = ['mrli']
3. 使用koa-session
koa-session是一个中间件
js
const session = require('koa-session');
const Koa = require('koa');
const app = new Koa();
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa.sess', /** (string) cookie key (default is koa.sess) */
/** (number || 'session') maxAge in ms (default is 1 days) */
/** 'session' will result in a cookie that expires when session/browser is closed */
/** Warning: If a session cookie is stolen, this cookie will never expire */
maxAge: 86400000,
autoCommit: true, /** (boolean) automatically commit headers (default true) */
overwrite: true, /** (boolean) can overwrite or not (default true) */
httpOnly: true, /** (boolean) httpOnly or not (default true) */
signed: true, /** (boolean) signed or not (default true) */
rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */
renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/
secure: true, /** (boolean) secure cookie*/
sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */
};
app.use(session(CONFIG, app));
// or if you prefer all default config, just use => app.use(session(app));
app.use(ctx => {
// ignore favicon
if (ctx.path === '/favicon.ico') return;
let n = ctx.session.views || 0;
ctx.session.views = ++n;
ctx.body = n + ' views';
});
app.listen(3000);
console.log('listening on port 3000');