傳統(tǒng)基于服務(wù)器的驗證方式
傳統(tǒng)的驗證方式是基于服務(wù)器的,就是把登陸信息存在服務(wù)端,每次登陸需要去辨別存儲的登陸信息,一般都是通過session來實現(xiàn), 這樣會有一些問題,比如每次認(rèn)證用戶發(fā)起請求時,服務(wù)器都需要創(chuàng)建一個用記錄來存儲信息,當(dāng)越來越多的用戶發(fā)起請求時,內(nèi)存的開銷也會不斷的增加
基于token的驗證原理
基于token的身份驗證是無狀態(tài)的,我們不用將信息存儲在服務(wù)器或者session中。token通過請求頭傳輸,而不是把認(rèn)證信息存儲在服務(wù)器或者session中,就意味著可以從任意一種可以發(fā)送HTTP請求的終端向服務(wù)器發(fā)送請求
實現(xiàn)步驟
- 登陸時,客戶端發(fā)送用戶名密碼
- 服務(wù)端驗證用戶名密碼是否正確,校驗通過就會生成一個有時效的token串,發(fā)送給客戶端
- 客戶端儲存token,一般都會存儲在localStorage或者cookie里面
- 客戶端每次請求時都帶有token,可以將其放在請求頭里,每次請求都攜帶token
- 服務(wù)端驗證token,所有需要校驗身份的接口都會被校驗token,若token解析后的數(shù)據(jù)包含用戶身份信息,則身份驗證通過,返回數(shù)據(jù)
node + jwt(jsonwebtoken) 搭建token身份驗證
npm i jsonwebtoken --save // 安裝jsonwebtoken模塊
// 引入模塊依賴
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
// 創(chuàng)建 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;
使用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) => {
// 根據(jù)用戶名查詢用戶
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:'賬號密碼錯誤'});
})
});
app.use(function (req, res, next) {
// 我這里知識把登陸和注冊請求去掉了,其他的多有請求都需要進(jìn)行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();
}
});
前端請求封裝
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)
});
}
)
}
}
公鑰可以使用openssl通過命令來生成
- window下生成命令
公鑰生成 genrsa -out rsa_private_key.pem 1024
私鑰生成 rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
- mac下生成命令
私鑰生成 openssl genrsa -out rsa_private_key.pem 1024
公鑰生成 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
使用node自帶的加密模塊crypto
const crypto = require('crypto')
function md5(content) {
crypto.createHash('md5').update(content).digest('hex')
}