nodejs 基于token的身份验证
最近接到一个需求,要求实现免密登陆,就是基于token实现的,于是就研究了下基于token验证身份的流程。
传统基于服务器的验证方式
传统的验证方式是基于服务器的,就是把登陆信息存在服务端,每次登陆需要去辨别存储的登陆信息,一般都是通过session来实现,我们比较老的项目都是通过存储session来实现登陆验证的。
这样会有一些问题,比如每次认证用户发起请求时,服务器都需要创建一个用记录来存储信息,当越来越多的用户发起请求时,内存的开销也会不断的增加
基于token的验证原理
基于token的身份验证是无状态的,我们不用将信息存储在服务器或者session中。
token通过请求头传输,而不是把认证信息存储在服务器或者session中,就意味着可以从任意一种可以发送HTTP请求的终端向服务器发送请求。
基于Token的身份验证的过程如下:
1. 登陆时,客户端发送用户名密码
2. 服务端验证用户名密码是否正确,校验通过就会生成一个有时效的token串,发送给客户端
3. 客户端储存token,一般都会存储在localStorage或者cookie里面
4. 客户端每次请求时都带有token,可以将其放在请求头里,每次请求都携带token
5. 服务端验证token,所有需要校验身份的接口都会被校验token,若token解析后的数据包含用户身份信息,则身份验证通过,返回数据
node + jwt(jsonwebtoken) 搭建token身份验证
下面是使用node + jwt搭建的基于token的身份校验
npm i jsonwebtoken // 安装jsonwebtoken模块
首先是服务端,新建一个js文件,我命名为jwt,内容如下:
// 引入模块依赖
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
// 创建 token 类
class Jwt {
constructor(data) {
this.data = data;
}
//生成token
generateToken() {
let data = this.data;
let created = Math.floor(Date.now() / 1000);
let cert = fs.readFileSync(path.join(__dirname, '../pem/private_key.pem'));//私钥 可以自己生成
let token = jwt.sign({
data,
exp: created + 60 * 30,
}, cert, {algorithm: 'RS256'});
return token;
}
// 校验token
verifyToken() {
let token = this.data;
let cert = fs.readFileSync(path.join(__dirname, '../pem/public_key.pem'));//公钥 可以自己生成
let res;
try {
let result = jwt.verify(token, cert, {algorithms: ['RS256']}) || {};
let {exp = 0} = result, current = Math.floor(Date.now() / 1000);
if (current <= exp) {
res = result.data || {};
}
} catch (e) {
res = 'err';
}
return res;
}
}
module.exports = Jwt;
然后我们在第一次登陆成功之后,生成对应token并返回给客户端
// 引入jwt token工具
const JwtUtil = require('../public/utils/jwt');
// 我这里的是aes加密密码的可以去掉
const AesUtil = require('../public/utils/aes');
// 登录
router.post('/login',(req,res) => {
var userName = req.body.user;
var pass = req.body.pass;
new Promise((resolve, reject) => {
// 根据用户名查询用户
users.findOne({'username':userName}).exec((err,result) => {
if(err){
reject(err);
}else{
resolve(result);
}
});
}).then((result) => {
console.log(result);
if(result){
// 密码解密 利用aes
var aes = new AesUtil(result.password);
var password = aes.deCryto();
if(pass == password){
// 登陆成功,添加token验证
let _id = result._id.toString();
// 将用户id传入并生成token
let jwt = new JwtUtil(_id);
let token = jwt.generateToken();
// 将 token 返回给客户端
res.send({status:200,msg:'登陆成功',token:token});
}else{
res.send({status:400,msg:'账号密码错误'});
}
}else{
res.send({status:404,msg:'账号不存在'})
}
}).catch((err) => {
console.log(err);
res.send({status:500,msg:'账号密码错误'});
})
});
服务端会对所有需要验证身份信息的请求接口进行拦截并校验token的合法性
app.use(function (req, res, next) {
// 我这里知识把登陆和注册请求去掉了,其他的多有请求都需要进行token校验
if (req.url != '/user/login' && req.url != '/user/register') {
let token = req.headers.token;
let jwt = new JwtUtil(token);
let result = jwt.verifyToken();
// 如果考验通过就next,否则就返回登陆信息不正确
if (result == 'err') {
console.log(result);
res.send({status: 403, msg: '登录已过期,请重新登录'});
// res.render('login.html');
} else {
next();
}
} else {
next();
}
});
前端代码,是我封装的fetch请求,token是存储在localStorage里面的,每个请求header里面都携带Token
/**
* Created by liu on 2018/5/9.
* fatch
* url:请求路径
* params:请求参数
*/
export default class FetchAsync {
// get
static getFatch(url) {
let geturl = url;
return new Promise((resolve, reject) => {
var url = 'http://127.0.0.1:3001/' + geturl;
fetch(url, {
method: 'GET',
header: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Token': localStorage.getItem('token')
},
}).then((response) => {
if (response.ok) {
return response.json();
} else {
reject({status: response.status})
}
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
}
)
}
// post
static postFatch(url, params) {
console.log(params);
var url = 'http://127.0.0.1:3001/' + url;
return new Promise((resolve, reject) => {
fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json;charset=utf-8",
'Token': localStorage.getItem('token')
},
body: JSON.stringify(params)
}).then(response => response.json()).then((res) => {
resolve(res);
}).catch((err) => {
reject(err)
});
}
)
}
}
至此,一个简单的token验证就可以使用了。
关于生成私钥和公钥的请移步至 https://blog.csdn.net/qq_37261367/article/details/81387839
本文纯手打,有不当之处请留言!如果对小伙伴们有帮助的话,点赞啊,谢谢!