無論是企業項目,仍是我的項目,一個優秀的系統必須具備鑑權的能力,何爲鑑權,是指驗證用戶是否擁有訪問系統的權利。前端
前端鑑權的方式也不少,例如 HTTP Basic Authentication、session-cookie、OAuth(開放受權)、JWT ···vue
本章經過node.js
vue
爲框架,模擬出一套較爲完整的先後端配合鑑權方案(採用JWT
鑑權理念)node
❗ PS:本章對基本知識不會有過多講解,主要對涉及技術的應用進行代碼演示ios
Token
值token
必須帶有時效性,過時則無效token
才能訪問Vue、Node.js 做爲先後端開發框架web
初始化咱們的框架,引入所須要的依賴庫,爲一切功能開發作好準備vue-router
😊 Tips : 關於mongoose的操做,不會進行解析;關鍵功能代碼會重點標識vuex
const express = require('express') // 引入 express
const app = express() // 實例化 express
const mongoose = require('mongoose') // 引入 mongoose
const db = require('./config/mongokey.js').mongoURI // 引入 數據庫路徑
const bodyParser = require('body-parser') // 引入 body-parser 做用:處理 post 請求
const passport = require('passport') // 做用:解析token
const port = process.env.PORT || 5000 // 設置端口號,本地爲5000
// 測試
// app.get('/', (req, res) => {
// res.send('Test,please ignore!')
// })
// 鏈接數據庫
mongoose.connect(db, { useNewUrlParser: true, useUnifiedTopology: true }).then(() => {
// success
console.log('Mongo Connect Successful')
}).catch((e) => {
// fail
console.log('Mongo Connect fail')
})
mongoose.set('useFindAndModify', false) // 屏蔽useFindAndModify廢棄警告
// 使用 body-parser 中間件 處理 POST 數據請求
app.use(bodyParser.urlencoded({
extended: false
}))
app.use(bodyParser.json())
// 初始化 passport 解析 token (關於passport配置請往下看 👇)
app.use(passport.initialize())
require('./config/passport')(passport)
// ---- CORS setHeader 跨域設置 ----
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Content-Type,Authorization");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
next();
})
/** * 引入路由表 & 使用路由 * @users 用戶相關 */
const users = require('./routes/Api/users')
app.use('/hdgc/users', users)
app.listen(port, () => {
console.log(`❤ Server running on port ${port} ❤`)
})
複製代碼
/**
* passport 配置文件
* @引入 passport-jwt
* @Strategy 策略
* @ExtractJwt
* @options jwtFromRequest 請求攜帶的token, secretOrKey 生成token時的加密名字
*/
const Strategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const User = require('../models/User') // 引入數據模型 // 須要用到 mongoose 中的 model
const options = {}
options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken()
options.secretOrKey = 'secretKey' // 這裏的secretOrKey須要與生成token時的加密命名一致,此處需注意!!
module.exports = passport => {
/**
* @jwt_payload 請求獲得的內容
* @done 表示策略結束,返回信息
*/
passport.use(new Strategy(options, (jwt_payload, done) => {
User.findById(jwt_payload.id).then(user => {
if (user) {
return done(null, user)
}
return done(null, false)
}).catch(err => {
console.log(err)
})
}))
}
備註:passport 會在接口處使用,將請求攜帶的token進行解析,而後判斷User中是否存在此用戶
存在則認證成功並將用戶返回,不然認證失敗
複製代碼
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs') // 加密插件
const jsonwebtoken = require('jsonwebtoken') // 生成 token
const passport = require('passport') // 解析token
const User = require('../../models/User') // 引入數據模型
/** * 用戶相關登陸、註冊接口 * @json * - code: 信息碼 * - data:數據 * - messgae:提示信息 * @表單驗證由前端處理 */
router.post('/register', (req, res) => {
console.log(req.body)
// 1- 判斷數據庫是否已存在該用戶名
User.findOne({
username: req.body.username
}).then((user) => {
if (user) {
return res.json({
code: '-1',
email: '用戶名已存在'
})
} else {
const newUser = new User({
username: req.body.username,
password: req.body.password
})
// 使用bcrypt對password加密處理
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
// hash - 加密後的密碼
if (err) {
// 加密異常捕獲
res.json({
code: '-1',
message: `密碼加密異常捕獲:${err}`
})
return
}
newUser.password = hash
// 存入數據庫
newUser.save().then(user => {
res.json({
code: '0',
data: user,
message: 'register successful'
})
}).catch(err => {
// 異常捕獲
res.json({
code: '-1',
message: `異常捕獲:${err}`
})
})
})
})
}
})
})
router.post('/login', (req, res) => {
console.log(req.body)
const username = req.body.username
const password = req.body.password
// 查詢當前用戶是否存在
User.findOne({
username: username
}).then((user) => {
if (!user) {
return res.json({
code: '-1',
message: '當前用戶未註冊'
})
}
// 使用bcrypt對加密密碼進行解密匹配
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
// 匹配成功
const rule = {
id: user.id,
username: user.username
}
/** * jsonwebtoken 參數意義 * @規則 * @加密名字 - 這個名字必須與passport配置的secretOrKey一致 * @過時時間 * @箭頭函數 * @返回token */
jsonwebtoken.sign(rule, 'secretKey', { expiresIn: 3600 }, (err, token) => {
if (err) {
// token生成異常捕獲
res.json({
code: '-1',
message: `token生成異常捕獲:${err}`
})
return
}
res.json({
code: '0',
data: user,
token: 'Bearer ' + token, // 必須在前面加上 'Bearer ' !!!!
message: 'Login successful'
})
})
} else {
// 匹配失敗
return res.json({
code: '-1',
message: '用戶名或密碼錯誤'
})
}
})
})
})
// 獲取當前用戶信息,須要進行鑑權認證!!!注意此處passport認證策略就是在passport.js中配置的
router.get('/', passport.authenticate('jwt', { session: false }), (req, res) => {
User.findOne({
username: req.user.username
}).then((user) => {
if (!user) {
return res.json({
code: '-1',
message: '用戶信息不存在'
})
}
req.user.password = '******' // user 爲 passport 執行 done() 所傳入的信息,注意password不能明文出現
res.json({
code: '0',
data: req.user,
message: 'success'
})
})
})
module.exports = router
複製代碼
import axios from 'axios'
import store from '../store'
/**
* Axios 基本配置封裝(默認配置,可被覆蓋)
* @baseURL 基礎路徑(前綴)
* @timeout 超時時間
* @responseType 響應數據類型 (json)
* @withCredentials 是否容許帶cookie等
* @header 根據不一樣請求設置
*/
const Axios = axios.create({
baseURL: '/',
timeout: 10000,
responseType: 'json',
withCredentials: true,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
// 請求攔截,一旦調用接口,將vuex中的loading設置爲true,顯示加載頁面
Axios.interceptors.request.use(config => {
store.dispatch('setLoading', true)
if (localStorage.Token) {
// 當緩存中存在Token時,將Token設置爲請求頭的 Authorization
config.headers.Authorization = localStorage.Token
}
return config
}, error => {
// 請求報錯時 loading 更新爲 false
store.dispatch('setLoading', false)
return Promise.reject(error)
})
// 響應攔截,一旦接口返回,將vuex中的loading設置爲false,顯示加載頁面
Axios.interceptors.response.use(response => {
store.dispatch('setLoading', false)
return response
}, error => {
// 響應報錯時 loading 更新爲 false
store.dispatch('setLoading', false)
return Promise.reject(error)
})
export default Axios
複製代碼
/**
* 存放 Api 接口文件,在頁面中直接引入對於接口,便可使用
* @userRegister 註冊接口
* registerInfo 註冊表單數據
* @userLogin 登陸接口
* loginInfo 登陸表單數據
* @getUserInfo 獲取登陸人信息 - 此接口須要進行鑑權認證,不經過是沒法調用
*/
import Axios from './http.js'
export function userRegister(registerInfo) {
return Axios({
url: '/hdgc/users/register',
data: registerInfo,
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export function userLogin(loginInfo) {
return Axios({
url: '/hdgc/users/login',
data: loginInfo,
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export function getUserInfo(username) {
return Axios({
url: '/hdgc/users/',
data: username,
method: 'get'
})
}
複製代碼
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store';
import jwt_decode from 'jwt-decode'
import { Message } from 'element-ui'
Vue.use(Router)
// ...
route.beforeEach((to, from, next) => {
// 若返回首頁,無須鑑權
if (to.path == '/') {
next()
} else {
/**
* 判斷當前是否存在Token
* @存在則進行鑑權判斷
* @不存在則返回首頁
*/
if (localStorage.Token) {
/**
* 判斷當前Token是否過時
* @過時則跳回首頁
* @未過時則成功跳轉
*/
const decoded = jwt_decode(localStorage.Token)
const currentTime = Date.now() / 1000
console.log('Token_Decode & currentTime', decoded, currentTime)
if (decoded.exp < currentTime) {
Vue.prototype.$notify({
title: 'Tips',
message: 'Token過時,從新登陸',
type: 'error',
duration: 3000
})
store.dispatch('clearCurrentState') // 清空vuex
next('/')
} else {
next()
}
} else {
Vue.prototype.$notify({
title: 'Tips',
message: '請先登陸!',
type: 'error',
duration: 3000
})
store.dispatch('clearCurrentState') // 清空vuex
next('/')
}
}
})
複製代碼
signinClcik:function(){
// loginInfo 爲參數
userLogin(this.loginInfo).then( res => {
if(res.data.code == 0){
// 獲取 token 存入緩存 (重點!!!)
const token = res.data.token
window.localStorage.setItem('Token',token)
// 更新受權狀態
this.$store.dispatch('setIsAuthenticated',true)
}else{
this.$notify({
title: 'Tips',
message: res.data.message,
type: 'error',
duration:3000
})
}
})
},
複製代碼
以上這套JWT
鑑權方案涉及先後端的一個配合,比較適合企業級,我的級的項目開發,權限認證較嚴格,所涉及的技術棧也比較簡單,特別適合新手練手,打造一個本身的鑑權系統數據庫
若是你們喜歡,但願爸爸們點個贊 😭express