1. 認識jwt(json web token)
- jwt是為了在網路應用環境傳遞宣告而執行的一種基於json的開放標準。
- jwt被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,簡單來說,就是用來驗證身份的手段,例如登入校驗,像我們之前用的cookie。
- jwt可以使用HMAC演算法或者是RSA的公私祕鑰對來進行簽名,來保證資訊的可靠性。
2. 應用場景
在例如身份驗證場景中,使用者一旦登入,接下來的每個請求都會包含jwt,用來驗證身份資訊。由於通訊雙方使用jwt對資料進行編碼,它的資訊是經過簽名的,所以可以確保資訊的安全性。
3. jwt對比cookie
cookie缺點
- 客戶端發請求給伺服器,伺服器種植cookie後,每次請求都會帶上cookie,浪費頻寬
- cookie不能跨伺服器訪問,不支援跨域
- 伺服器要對登入的使用者物件進行儲存,浪費伺服器記憶體
jwt優點
- jwt是不基於狀態的,不需要每次請求都帶上token,節約流量
- 伺服器不需要佔用記憶體,資訊相對於可靠些
- 可以跨服務端,可以共用
4. jwt結構
- Header頭部:{typ:`jwt`,alg:`HS256`} alg:當前用的什麼演算法加密的;使用Base64Url編碼組成了JWT結構的第一部分
- PlyLoad負載:存放有效資訊的地方
- Signature簽名:建立簽名需要使用編碼後的header和payload以及一個祕鑰;例如如果希望使用HMAC SHA256演算法,那麼簽名應該使用下列方式建立
HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
完整的jwt格式的輸出是以 . 分隔的三段Base64編碼
金鑰secret是儲存在服務端的,服務端會根據這個金鑰進行生成token和驗證,所以需要保護好。
5. 舉個例子
express+vue+mongoose
後端app.js,包括註冊,登入,獲取訂單介面
let express = require(`express`)
let bodyParser = require(`body-parser`)//中介軟體
let jwt = require(`jwt-simple`)//jwt庫
//資料庫
let User = require(`./model/user`)
//監聽函式
let app = express()
let {secret} = require(`./config`)
//中介軟體一定是函式,處理髮回來的json型別,還有text,urlencoded(a=b&c=d)
app.use(bodyParser.json())
//防止跨域 request請求 response響應
app.use(function(req, res, next){
res.setHeader(`Access-Control-Allow-Origin`,`*`);//簡單點,接收所有
res.setHeader(`Access-Control-Allow-Headers`,`Content-type,Authorization`);
res.setHeader(`Access-Control-Allow-Methods`,`GET,POST,DELETE,PUT,OPTIONS`);
if(req.method === `OPTIONS`) {
res.end()
}else {
next()
}
})
//註冊
app.post(`/reg`, async function(req, res, next){
let user = req.body;
try {
user = await User.create(user) //在資料庫中插入資料
res.json({
code: 0,
data: {
user: {
id: user._id,
username: user.username
}
}
})
} catch (error) {
res.json({
code: 1,
data: `註冊失敗`
})
}
})
//登入
app.post(`/login`, async function(req,res,next){
let user = req.body;
user = await User.findOne(user)//資料庫中查詢
if(user) {
let token = jwt.encode({//編碼
id: user._id,
username: user.username
},secret);
res.json({//返回資訊
code: 0,
data: { token }
})
}else {
res.json({
code: 1,
data: `使用者不存在`
})
}
})
// 使用者校驗 中介軟體
let auth = function(req, res, next){
//post模擬時 新增Headers Authorization: Bearer token的值
let authorization = req.headers[`authorization`]
if(authorization) {
let token = authorization.split(` `)[1];
try {
//看token是否合法,解碼,如果串改過token就解不出來,進入異常頁面
let user = jwt.decode(token, secret);
req.user = user;//後面就可以拿到user,中介軟體用法
next();//下一步
} catch (error) {
console.log(error)
res.status(401).send(`Not Allowed`)
}
} else {
res.status(401).send(`Not Allowed`);
}
}
//傳送請求,看看能不驗證成功auth,如果可以拿到返回資料
app.get(`/order`, auth, function(req,res,next){
res.json({
code: 0,
data: {
user: req.user
}
})
})
app.listen(3000)
複製程式碼
資料庫頁面
// 運算元據庫
let mongoose = require(`mongoose`);
let {DB_URL} = require(`../config`);
mongoose.connect(DB_URL,{useNewUrlParser:true})
/**
* 連線成功
*/
mongoose.connection.on(`connected`, function () {
console.log(`Mongoose connection open to ` + DB_URL);
});
/**
* 連線異常
*/
mongoose.connection.on(`error`,function (err) {
console.log(`Mongoose connection error: ` + err);
});
//建立Schema資料模型
let UsrSchema = new mongoose.Schema({
username: String,
password: String
});
module.exports = mongoose.model(`User`, UsrSchema);
複製程式碼
axios簡單封裝
import axios from `axios`
import router from `../src/router`
axios.defaults.baseURL = `http://localhost:3000`
//axios 攔截器對拿到的資料進行攔截
axios.interceptors.response.use(function(res){
if(res.data.code !== 0) {
return Promise.reject(res.data.data)
}
return res.data;
},res=>{
if(res.response.status === 401){ // 沒許可權跳到登入頁
router.history.push(`/login`);
}
return Promise.reject(`Not Allowed`);
});
//對傳送的請求統一加上token,來驗證是否是本人登入
axios.interceptors.request.use(function(config){
let token = localStorage.getItem(`token`)
if(token) {
config.headers.Authorization = `Bearer ${token}`
}
return config;
})
export default axios
複製程式碼
config.js
module.exports = {
`DB_URL`: `mongodb://localhost:27017/jwt`,
`secret`: `jeffywin`//祕鑰 加鹽
}
複製程式碼
前臺介面vue-cli腳手架,沒什麼說的,登入介面
<template>
<div class="main">
<div class="item">
<div style="width:100px">登入頁</div>
<input type=`text` v-model=`user.username`/>
</div>
<div class="item">
<div style="width:100px">密碼</div>
<input type=`text` v-model=`user.password`/>
</div>
<button @click="login">提交</button>
</div>
</template>
<script>
import axios from `../../utils/axios`
export default {
data() {
return {
user: {
username: ``,
password: ``
}
}
},
methods: {
login() {
axios.post(`/login`,this.user).then(res => {
localStorage.setItem(`token`, res.data.token)//登入後儲存token
this.$router.push(`/order`)
})
}
}
}
</script>
<style scoped lang="scss">
.main {
margin: 0 auto;
width: 300px;
.item {
display: flex;
margin-bottom: 10px;
}
}
</style>
複製程式碼
order介面
<template>
<div class="order">
<h1>This is an order page</h1>
{{username}}//如果登入成功,跳轉order介面,拿到登入的使用者
</div>
</template>
<script>
import axios from `../../utils/axios`
export default {
data() {
return {
username: ``
}
},
mounted() {
axios.get(`/order`).then(res => {
this.username = res.data.user.username
})
},
}
</script>
複製程式碼
原始碼在本人github
github.com/jeffywin/jw…