為什麼使用token
session
當使用者第一次通過瀏覽器使用使用者名稱和密碼訪問伺服器時,伺服器會驗證使用者資料,驗證成功後在伺服器端寫入session資料,向客戶端瀏覽器返回sessionid,瀏覽器將sessionid儲存在cookie中。
當使用者再次訪問伺服器時,會攜帶sessionid,伺服器會進行使用者資訊查詢,查詢到,會將查詢到的使用者資訊返回,從而實現狀態保持。
token
token與session的不同主要在認證成功後,會對當前使用者資料進行加密,生成一個加密字串token,返還給客戶端,由客戶端儲存。
再次訪問時伺服器端對token值的處理:伺服器對瀏覽器傳來的token值進行解密,解密完成後進行使用者資料的查詢,如果查詢成功,則通過認證,實現狀態保持。
相比較而言,token很大程度上減少了服務端的壓力,伺服器不用儲存那麼多sessionid,只需要對token解密就可以了,也不用儲存使用者在哪個伺服器登入的,只需要jwt加密規則相同就行。
node js實現token
使用到的包:
express@next
:開啟伺服器cors
: 開啟跨域請求dotenv
: 應用env檔案mongoose
: 連線MongoDBbcrypt
: bcrypt雜湊加密,或者使用bcryptjs,相容性好,不過效能差一點jsonwebtoken
: 無屬性token生成
思路:
- 為了保證使用者密碼的安全性,使用者的密碼存入到資料庫之前應該被加密
- 使用者登入時,輸入的是密碼,需要解析一下和資料庫裡面存的雜湊資料對比
- 使用者登入成功後,需要向客戶端傳送token,客戶端儲存下來
- 客戶端發起的每一個需要登入驗證的介面,都需要在請求裡面帶上token,後端解析驗證通過再做後面的業務邏輯
- 先建立一個express的服務
require('dotenv').config(); // 使用env檔案
const express = require('express')
const bcrypt = require('bcryptjs') // 雜湊加密
const jwt = require('jsonwebtoken') // jwt加密token
const app = express()
const { User: UserModel } = require('./model')
app.use(express.json()) // 處理請求
app.use(require('cors')()) // 處理跨域
app.listen(5000, () => {
console.log(`app is start at localhost:5000`)
})
複製程式碼
用一個簡單的model User來實現這個樣例
建立model.js檔案 匯出一個User Model
const mongoose = require('mongoose')
mongoose.connect(process.env.db, {
useCreateIndex: true,
useNewUrlParser: true
})
const UserSchema = new mongoose.Schema({
name: {
type: String,
unique: true, // 不可重複
},
pwd: {
type: String
}
})
const User = mongoose.model('User', UserSchema)
module.exports = { User }
複製程式碼
註冊邏輯
註冊需要處理的事情就是將使用者密碼雜湊加密,存入資料庫中
brypt加密,即使是同一個字串,每次加密生成的密文都是不一樣的
bcrypt雜湊有兩種方式,一種同步的hashSync(需要加密的字串, 密碼強度)
,一種非同步的hash(需要加密的字串, 密碼強度, 回撥函式)
密碼強度不宜太高,太高了效能低。太低了,加密力度又不夠,一般8-12差不多。
app.post('/api/register', async (req, res) => {
const { name, pwd } = req.body
if (!name || !pwd) return res.send('缺少引數')
// 雜湊使用者密碼
const bcryptPwd = bcrypt.hashSync(pwd, 10)
const user = await UserModel.create({
pwd: bcryptPwd,
name,
})
if (!user) return
res.send(user)
})
複製程式碼
登陸邏輯
登陸時需要用到brypt的compareSync(解密字串, 已經加密的對應字串)
或者compare(解密字串, 已經加密的對應字串).then(res)
來解密pwd,也只有這玩意兒能解密了。
如果解密通過了,需要呼叫jwt.sign生成token傳給前端,由前端儲存登陸憑據
sign接收一個目標物件和一個加密的secret:
- 目標物件就是需要傳給客戶端的用於登陸驗證的資訊
- secret則儲存在伺服器,加密解密都需要這個欄位,不應該被外界知曉
app.post('/api/login', async (req, res) => {
const { name, pwd } = req.body
const user = await UserModel.findOne({ name })
if (!user)
return res.status(422).send({ errmsg: '使用者不存在' })
// 驗證密碼 compareSync
const isPass = await bcrypt.compareSync(pwd, user.pwd)
if (!isPass)
return res.status(422).send({ errmsg: '密碼錯誤' })
// 驗證通過 給客戶端返回token 以備後面驗證登陸
// 生成token
const token = jwt.sign({
id: String(user._id),
unUse: '隨便加的欄位,不用在意'
}, process.env.SECRET)
res.send({
success: '登陸成功',
token
})
})
複製程式碼
驗證登陸
首先,我們肯定有很多介面都需要登陸驗證,那我們肯定不可能在每一個介面裡面都去處理token,更好的方法就是封裝一個函式用於處理這個token。
那麼express提供了一個很好的解決方案:中介軟體
建立好中介軟體之後,之後每一個需要驗證許可權的介面,都使用這個中介軟體即可
增加一個許可權驗證中介軟體:
- jwt一般來說都放在header裡面的
Authorization
,在後端只需要在headers欄位中拿到token - 拿到token之後使用
verify(token, seceret)
解密 - 查詢使用者
- 找到了就進行下一步,沒找到就返回錯誤
const authMiddleWare = async (req, res, next) => {
const { authorization } = req.headers
const token = String(authorization).split(' ')[1]
if (!token) return res.send({ errmsg: 'token已過期' })
// 解析token
const { id } = jwt.verify(token, process.env.SECRET)
const user = await UserModel.findById(id)
if (user) {
req.user = user
next()
} else {
res.send({
errmsg: 'token已過期'
})
}
}
複製程式碼
使用這個中介軟體
// 獲取個人資訊
app.get('/api/getUserInfo', authMiddleWare, async (req, res) => {
res.send(req.user)
})
複製程式碼
思路清晰,步驟簡單。當然這裡只是記錄jwt,bcrypt的使用,很多容錯處理就沒有省略了,在實際的業務中肯定還是得加上的。另外,.env這個檔案在實際專案中肯定也得忽略上傳的,這應該是一個私有檔案。