Token驗(yàn)證的基本流程
1.服務(wù)端收到請(qǐng)求,去驗(yàn)證用戶名與密碼
2.驗(yàn)證成功后旗国,服務(wù)端會(huì)簽發(fā)一個(gè) Token邻悬,再把這個(gè) Token 發(fā)送給客戶端
3.客戶端收到 Token 以后可以把它存儲(chǔ)起來搏明,比如放在 Cookie 里或者 Local Storage 里
4.客戶端每次向服務(wù)端請(qǐng)求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token
5.服務(wù)端收到請(qǐng)求省艳,然后去驗(yàn)證客戶端請(qǐng)求里面帶著的 Token瓜挽,如果驗(yàn)證成功颁股,就向客戶端返回請(qǐng)求的數(shù)據(jù)
Node.js 實(shí)現(xiàn)token么库,需要的插件
//生成token與驗(yàn)證
1.jsonwebtoken
npm install jsonwebtoken --save
* 生成token的方法 sign
* 驗(yàn)證token的方法 verify
2.express-jwt
npm install express-jwt --save
* 驗(yàn)證token是否過期并規(guī)定那些路由不需要驗(yàn)證 express-jwt({})
JWT標(biāo)準(zhǔn)的Token有如下三個(gè)部分
- header (頭部)
- payload (數(shù)據(jù))
- signature (簽名)
形式如下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInVzZXJfaWQiOjEsImlhdCI6MTU5NDI2MjQ5NSwiZXhwIjoxNTk0MzQ4ODk1fQ.1MJ_MAFgpBjOjpggj69Xz8F_evBcMAenRK_7a8fdVrc
具體實(shí)現(xiàn)過程
-
目錄格式
- 在目錄下新建token文件夾,并創(chuàng)建token.js文件
var jwt = require('jsonwebtoken');
var jwtScrect = 'zgs_first_token'; //簽名
//登錄接口 生成token的方法
var setToken = function (user_name, user_id) {
return new Promise((resolve, reject) => {
//expiresln 設(shè)置token過期的時(shí)間
//{ user_name: user_name, user_id: user_id } 傳入需要解析的值( 一般為用戶名甘有,用戶id 等)
const token = jwt.sign({ user_name: user_name, user_id: user_id }, jwtScrect, { expiresIn: '24h' });
resolve(token)
})
}
//各個(gè)接口需要驗(yàn)證token的方法
var getToken = function (token) {
return new Promise((resolve, reject) => {
if (!token) {
reject({
error: 'token空'
})
}
else {
//第二種 改版后的
var info = jwt.verify(token.split(' ')[1], jwtScrect);
resolve(info); //解析返回的值(sign 傳入的值)
}
})
}
module.exports = {
setToken,
getToken
}
!!!在app.js中自定義方法 驗(yàn)證token是否過期诉儒,如果沒過期,則解析出用戶信息返回req.data, 通過req.data判斷token
//引入插件
var vertoken=require('./token/token')
var expressJwt=require('express-jwt')
//解析token獲取用戶信息
app.use(function(req, res, next) {
//注意此處token亏掀;authorization設(shè)置要與設(shè)置一致否則可能出現(xiàn)token為空
var token = req.headers['authorization'];
if(token == undefined){
return next();
}else{
vertoken.getToken(token).then((data)=> {
req.data = data; //解析成功后返回設(shè)置基本信息(通過req.data判斷是否過期)
return next();
}).catch((error)=>{
return next();
})
}
});
//驗(yàn)證token是否過期并規(guī)定那些路由不需要驗(yàn)證
app.use(expressJwt({
secret:'zgs_first_token',
algorithms:['HS256']
}).unless({
path:['/login','/register'] //不需要驗(yàn)證的接口名稱
}))
//設(shè)置托管靜態(tài)目錄; 項(xiàng)目根目錄+ public.可直接訪問public文件下的文件eg:http://localhost:3000/images/url.jpg
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/', usersRouter);
//token失效返回信息
app.use(function(err,req,res,next){
if(err.status==401){
return res.status(401).send('token失效')
//可以設(shè)置返回json 形式 res.json({message:'token失效'})
}
})
- 在登陸時(shí)生成token
//引入token
var vertoken=require('../token/token')
/* user login. */
router.post('/login', function (req, res) {
//獲取參數(shù)
const params = [];
params[0] = req.body.user_name
params[1] = req.body.password
console.log(params)
if (params[0] === '' || params[1] === '') {
return res.json({
code: 1,
message: '賬戶或密碼不能為空'
})
} else {
// 查詢數(shù)據(jù)是否存在數(shù)據(jù)庫中(user表沒有設(shè)置索引)
connection.query(sqlobj.sqls.login, params, function (err, result) {
if (err) {
throw err;
} else {
if (result.length != 0) { //else判斷哪里出錯(cuò)
console.log(result)
//調(diào)用生成token的方法
vertoken.setToken(result[0].user_name,result[0].user_id).then(token=>{
return res.json({
code: 200,
message: '登錄成功',
token:token
//前端獲取token后存儲(chǔ)在localStroage中,
//**調(diào)用接口時(shí) 設(shè)置axios(ajax)請(qǐng)求頭Authorization的格式為`Bearer ` +token
})
})
}
}
}
});
- 驗(yàn)證token 及調(diào)用
//sql語句
var sqls={
login: "select * from users where user_name = ? and password = ? ",
users:'select user_name,password from users where user_name = ?',
}
//封裝需要token驗(yàn)證的方法忱反,
var conn_query = function (req, res, sql, params) {
connection.query(sql, params, function (err, result) {
if (err) {
//遇到過的錯(cuò)誤(可直接throw err)
let messageError = '';
if (err.errno == 1048) {
messageError = '參數(shù)不能為空'
} else if (err.errno == 1062) {
messageError = '數(shù)據(jù)已存在'
} else if (err.errno == 1265) {
messageError = '格式錯(cuò)誤'
}
else {
messageError = '請(qǐng)求失敗'
}
return res.json({
code: err.errno,
message: messageError
})
} else {
//第二種 改版后req.data 是驗(yàn)證后的返回值 (生成token傳的數(shù)據(jù))
if (req.data) {
return res.json({
code: 200,
message: 'success',
data: result
})
} else {
return res.json({
code: 1,
status: 401,
message: "error"
})
}
}
})
};
//導(dǎo)出
module.exports = {
sqls,
conn_query
}
/#在其他文件接口中調(diào)用#/
// 先引入該文件
var sqlobj = require('../db/sql'); //目錄結(jié)構(gòu)在上方
router.post('/api/addWebsite', function (req, res, next) {
const params = [req.body.name, req.body.url, req.body.alexa, req.body.country];
sqlobj.conn_query(req, res, sqlobj.sqls.addWebsite, params)
})
寫在最后
在token失效時(shí)返回status:401,前端清除token 根據(jù)status狀態(tài)碼返回登錄頁面
完整代碼 Github