后台管理系统中最简单的权限控制就是【登陆校验】。
在以前,包括现在的大部分中小型项目中依然是用的cookie、session进行校验。—— 这两个浏览器早期就实现了的API带给了我们很多便利。
比如笔者在某vue项目后台设置了:
let tokenkey = 'a98sdyauefbh329'
app.get('/api/login', (req, res) => {
const {
username,
password
} = req.query
if (username == 'yun' && password == '123456' || username == 'tim' && password == '123456') {
res.json({
code: 0,
message: '登录成功',
token: tokenkey + '-' + username + '-' + (new Date().getTime() + 60 * 60 * 1000)
})
} else {
res.json({
code: 1,
message: '账号/密码错误'
})
}
})
对超级管理员请求成功后设置token —— 它其实在前端会保存到浏览器cookie里(前端判断code是否为0,若是,则调用api保存token),用于下一次登录时校验“在一定时间内是否已登录过”:
axios.interceptors.request.use(config=>{
if(判断有没有token){
config.headers.token=store.state.token
}
return config
})
这里笔者用了axios请求拦截:在再次发起请求时,判断有没有保存过token —— 如果有,就携带到header里返回给client。
express有一个非常强大的模块:cookie-session
npm install cookie-session
使用时直接引入:
const cookieSession=require('cookie-session');
(function (){ //用立即执行函数防止污染全局
var keys=[];
for(var i=0;i<100000;i++){
keys[i]='a_'+Math.random();
}
server.use(cookieSession({
name: 'sess_id',
keys: keys,
maxAge: 20*60*1000 //20min
}));
})();
在任意页面进入时判断:
//检查登录状态
router.use((req, res, next)=>{
if(!req.session['admin_id'] && req.url!='/xxx'){ //没有登录且当前不是登录页(避免redirect黑洞)
res.redirect('/admin/login');
}else{
next();
}
});
若是登录成功,则往session中添加相应值:
req.session['admin_id']=data[0].ID;
Koa是一个简洁的框架,把许多小功能都拆分成了中间件,用一个洋葱模型保证了中间件丰富的可拓展性,我们要使用Session来保持登录状态,就需要引用Session中间件。
koa中
new koa()
只负责中间件,一切路由事宜全权交与new Router()
处理
npm install koa-session --save
使用时依然要先配置:
const session_signed_key = ["some secret hurr"]; // 这个是配合signed属性的签名key
const session_config = {
key: 'koa:sess', /** cookie的key。 (默认是 koa:sess) */
maxAge: 4000, /** session 过期时间,以毫秒ms为单位计算 。*/
autoCommit: true, /** 自动提交到响应头。(默认是 true) */
httpOnly: true, /** 是否设置HttpOnly,如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。 (默认 true) */
signed: true, /** 是否签名。(默认是 true) */
rolling: true, /** 是否每次响应时刷新Session的有效期。(默认是 false) */
renew: false, /** 是否在Session快过期时刷新Session的有效期。(默认是 false) */
};
const Koa_Session = require('koa-session'); // 导入koa-session
// 实例化
const app = new Koa();
const session = Koa_Session(session_config, app)
app.keys = session_signed_key;
// 使用中间件,注意有先后顺序
app.use(session);
app.use(async (ctx,next)=>{
const databaseUserName = "yunxiaomeng";
const databaseUserPasswd = "yunxiaomengnb";
if (!ctx.session.logged) { // 如果登录属性为undefined或者false,对应未登录和登录失败
// 设置登录属性为false
ctx.session.logged = false;
// 如?nickname=post&passwd=123解析为{nickname:"post",passwd:"123"}
let query = ctx.request.query;
// 判断用户名密码是否为空
if (query.nickname && query.passwd) {
// 比对并分情况返回结果
if (databaseUserName == query.nickname) { // 如果存在该用户名
// 进行密码比对并返回结果
ctx.body = (databaseUserPasswd == query.passwd) ? "登录成功" : "用户名或密码错误";
ctx.session.logged = true;
} else { // 如果不存在该用户名
ctx.body = "用户名不存在";
}
} else {
ctx.body = "用户名密码不能为空";
}
} else {
ctx.body = "已登录";
}
})
const jwt = require('jsonwebtoken');
router.post('/login', async (ctx) => {
const data = ctx.request.body;
if(!data.name || !data.password){
return ctx.body = {
code: '2',
data: null,
msg: '参数不合法'
}
}
const result = await userModel.findOne({
name: data.name,
password: data.password
})
if(result !== null){
const token = jwt.sign({
name: result.name,
_id: result._id
}, 'my_tokena', { expiresIn: '2h' });
return ctx.body = {
code: '0',
data: token,
msg: '登录成功'
}
}else{
return ctx.body = {
code: '2',
data: null,
msg: '用户名或密码错误'
}
}
});
然后前端可以在请求时获取token并保存:
axios.post('/login', {
name: this.username,
password: this.password
}).then(res => {
if(res.code === '0'){
localStorage.setItem('token', res.data);
this.$router.push('/');
}else{
alert(res.msg);
}
})
然后请求服务器端API的时候,把 token 带在请求头中传给服务器进行验证。每次请求都要获取 localStorage 中的 token,这样很麻烦,这里使用了 axios 的请求拦截器,对每次请求都进行了取 token 放到 headers 中的操作。
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
config.headers.common['Authorization'] = 'Bearer ' + token;
return config;
})
const koajwt = require('koa-jwt');
// 错误处理
//(后端)校验
app.use(koajwt({ //这个插件会自动找“请求头header”里的对应值
secret: 'my_token'
}).unless({
path: [/\/user\/login/]
}));
通过 app.use 来调用该中间件,并传入密钥 {secret: 'my_token'}
,unless 可以指定哪些 URL 不需要进行 token 验证。token 验证失败的时候会抛出401错误,因此如果需要添加错误处理,要放在 app.use(koajwt())
之前,否则不执行。
如果请求时没有token或者token过期,则会返回401。
其实jwt自身也有校验api:
jwt.verify(token,secret)
token就是传递过来的token;secret就是在设置token的时候使用到的密匙