NodeJS實作JWT原理
- 1.會話管理
- 2.session和cookies
- 3.JWT定義
- 4.JWT原理
- 5.JWT認證流程
- 6.代碼實作(Vue+NodeJS+MySql)
1.會話管理
我們用nodejs為前端或者其他服務提供resful介面時,http協議他是一個無狀態的協議,有時候我們需要根據這個請求的背景關系獲取具體的用戶是否有權限,針對用戶的背景關系進行操作,所以出現了cookies session還有jwt這幾種技術的出現, 都是對HTTP協議的一個補充,使得我們可以用HTTP協議+狀態管理構建一個的面向用戶的WEB應用,
2.session和cookies
個人理解,cookie是靠session_id完成服務端與客戶端的通信的,當我們第一次登錄時,服務端會去尋找我們的登錄資訊,然后將登錄資訊回寫到cookie中,而session_id就是我們尋找登錄資訊的一個標識,
有幾點需要注意一下:
1.cookie資料存放在客戶的瀏覽器上,session資料放在服務器上,
2.cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙,考慮到安全應當使用session,
3.session會在一定時間內保存在服務器上,當訪問增多,會比較占用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie,
4.單個cookie保存的資料不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie,而session則存盤與服務端,瀏覽器對其沒有限制,
5.cookie在多個站點下會存在跨域問題,
3.JWT定義
JWT全稱(json web token),就是我們開發中常用到的Token,
4.JWT原理
既然Token是對Http協議的一個補充,可以說代替Cookie,那么Token也是要存盤用戶資訊的,而在cookie中都是以鍵值對形式存盤的:比如(name=‘lgh’; )一定是以分號加一個空格結尾,而Token一般是服務端回傳給客戶端的一個JSON,但是我們與服務端通信的時候,往往這個JSON會加上簽名(也就是一串你不認識的英文,服務端認識),
5.JWT認證流程
瀏覽器發起請求登陸,攜帶用戶名和密碼;
服務端根據用戶名和明碼到資料庫驗證身份,根據演算法,將用戶識別符號打包生成 token,
服務器回傳JWT資訊給瀏覽器,JWT不應該包含敏感資訊,這里有一個加密程序,這是很重要的一點,
瀏覽器發起請求獲取用戶資料,把剛剛拿到的 Token一起發送給服務器,一般放在header里面,
服務器發現資料中有 Token,服務端對Token解密,然后去對比,驗證通過,
服務器回傳該用戶的用戶資料,
服務器可以在payload設定過期時間, 如果過期了,可以讓客戶端重新發起驗證,
6.代碼實作(Vue+NodeJS+MySql)
場景:比如我現在正在做的一個商城專案,一般首頁,產品頁,給客戶看得界面,都是不需要身份校驗的,我們逛淘寶京東,就算沒有登錄我們也能瀏覽商品,但是當我們點購買,點擊購物車一些入口時,這時候是需要身份校驗的,你必須登錄了才能看自己購物車資訊,才能購買商品,還有一點,cookie存盤在客戶端,我們會話視窗一關,cookie就沒了,然后我們再去點購物車,服務端肯定需要我們再次登錄,Token就是解決了這個問題,
1.連接資料庫
這里我就簡單寫了兩張表,一張user,一張cart
2.搭建服務器
app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors=require("cors");
// 引入路由模塊(用戶)
const user=require("./routes/users");
/*引入token的模塊*/
const jwt=require("./jwt.js")
const app = express();
app.listen(8080);
app.use(cors({
origin:['http://localhost:8081'],
credentials:true
}));
3.連接池
pool.js
//創建mysql連接池
const mysql = require('mysql');
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '',
database: 'user',
connectionLimit: 10
});
//把創建好的連接池匯出
module.exports = pool;
4.JWT模塊(Token)
進入路由模塊之前,需要用到一個簽名工具,(私鑰加密、公鑰解密)
這里我也寫好了
// 引入模塊依賴
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
//生成token
function generateToken(data){
// 隨機生成一個時間戳
let created = Math.floor(Date.now() / 1000);
// 讀取硬碟上的私鑰檔案,利用jwt對其簽名,這里會生成token
let cert = fs.readFileSync(path.join(__dirname, './pem/rsa_private_key.pem'));//私鑰 可以自己生成
let token = jwt.sign({
data, // 需要加密的用戶資訊
exp: created + 60 * 60,// 設定token過期時間
}, cert, {algorithm: 'RS256'}); // 這是一個簽名演算法,這里不過多介紹
return token;
}
// 校驗token
function verifyToken(token) {
let cert = fs.readFileSync(path.join(__dirname, './pem/rsa_public_key.pem'));//公鑰 可以自己生成
let res;
try {
if(token!==undefined){
let result = jwt.verify(token, cert, {algorithms: ['RS256']}) || {};
res = result.data || {};
}
} catch (e) {
res = e;
}
return res;
}
module.exports = { generateToken, verifyToken };
加密解密模塊就完成了,我們需要把方法暴露出來用module.exports方法
5.路由模塊(介面)
user.js
const express=require("express");
const router=express.Router();
const pool=require("../pool");
const jwt=require("../jwt.js")
// 登錄介面
router.post("/islogin",(req,res)=>{
var {uname,upwd,remember}=req.body;
var sql="select * from user_user where uname=? and binary upwd=?";
pool.query(sql,[uname,upwd],(err,result)=>{
err&&console.log(err);
if(result.length>0){
res.write(JSON.stringify({
ok:1,uname:result[0]['uname'],
remember:remember||false,
token:jwt.generateToken(result[0])
}));
}else{
res.write(JSON.stringify({ok:0,msg:"用戶名或密碼錯誤!"}));
}
res.end();
})
})
// 購物車介面
router.get("/orders",(req,res)=>{
var aid=req.user.uid
if(aid==null){
res.write(JSON.stringify({ok:0}));
res.end();
}else{
var sql="select * from user_order where aid=?";
pool.query(sql,[aid],(err,result)=>{
res.write(JSON.stringify({ok:1,data:result[0]}));
res.end();
})
}
})
module.exports=router;
6.Vue模塊
在Vue里面我們需要考慮兩個東西,
第一個是界面(簡單的做了一下)
第二個是Axios
因為在訪問個人,購物車等入口時,我們需要在每次請求中帶上Token向后端發請求,每次都寫Token太麻煩了,所以這里就用到了Axios攔截器,
axios.js
這里存在一個邏輯問題,當用戶第一次登陸才需要校驗,
登陸成功之后,服務端回傳一個Token
if(result.length>0){
res.write(JSON.stringify({
ok:1,uname:result[0]['uname'],
remember:remember||false,
token:jwt.generateToken(result[0])
}));
用戶登陸成功,服務端回傳一個Token,為了保持登陸狀態可以看快取中有沒有Token,
1.登陸成功,快取中存一個token以及Vuex中存盤用戶資訊
2.頁面關閉怎么辦,發起請求時先去快取中找Token,然后帶在請求頭中,如果找不到Token,發起請求時,服務端校驗,
校驗程序如下:用Node中間件,在還沒進入到路由之前做了一個攔截,
3.如果校驗失敗,一定要從快取中移除Token,知道下一次登錄成功再存進去
也就是app.use('/user',user)之前
app.use((req, res, next)=>{
if (req.url != '/user/islogin' && (req.url.startsWith("/user") || req.url.startsWith("/orders"))) {
let token = req.headers.token;
let result = jwt.verifyToken(token);
// 如果考驗通過就next,否則就回傳登陸資訊不正確
console.log(result)
if(result === undefined){
res.send({status:403, msg:"未提供證書"})
}else if (result.name == 'TokenExpiredError') {
res.send({status: 403, msg: '登錄超時,請重新登錄'});
} else if (result.name=="JsonWebTokenError"){
res.send({status: 403, msg: '證書出錯'})
} else{
req.user=result;
next();
}
} else {
next();
}
const Axios=axios.create({
baseURL:"http://localhost:8080",
withCredentials:true
})
Axios.interceptors.request.use(
config=>{
// post請求不能直接發物件引數,這里用qs模塊轉一下
if(config.method ==="post"){
config.data=qs.stringify(config.data)
}
if(localStorage.getItem("token")){
config.headers.token=localStorage.getItem("token");
}
if(sessionStorage.getItem("token")){
config.headers.token=sessionStorage.getItem("token");
}
return config;
},
error=>{
console.log(error);
Promise.reject(error);
}
);
Axios.interceptors.response.use(
res=>{
if(res.data.status==403){
localStorage.removeItem("token");
sessionStorage.removeItem("token");
console.log(403)
}else if(res.data.ok==-1){
alert(res.data.msg+" 請先登錄 !");
console.log(-1)
}else if(res.data.token){
console.log(res.data.token)
if(res.remember==="true"){
localStorage.setItem("token",res.data.token);
}else{
sessionStorage.setItem("token",res.data.token);
}
}
return res;
},
error=>{
}
)
export default {
install: function(Vue, Option){
Vue.prototype.$axios=Axios;
}
}
效果圖如下:
1.登錄

登錄成功后回傳一個Token

2.訪問購物車
在右側看到請求頭中帶了一個Token,這里驗證是通過了的,(代碼都是從自己專案里面copy的,很久之前自己練手寫的一個demo)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/202842.html
標籤:其他
