登錄認證 (鑑權),是每一個應用都須要的基礎功能。但不少的時候,卻都被你們所忽略,不只安全漏洞嚴重,並且代碼緊耦合,混亂不堪。
Passport & JWT,正是爲了解決登錄認證的事情,讓認證模塊更透明,減小耦合!前端
網上關於 JSONWebToken (如下簡稱 JWT ) && passport.js的中文學習資料較少,學習的時候還蠻吃力的。因此總結出此篇,文章如有錯謬,歡迎指出,我會及時更正。vue
轉載請註明出處: https://blog.csdn.net/q955488...node
JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用JWT在用戶和服務器之間傳遞安全可靠的信息。
一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。
想了解更多關於 JWT 的,請查看個人另一篇文章: 全棧之初識JWT -- Web安全的守護神webpack
passport.js是Nodejs中的一個作登陸驗證的中間件,極其靈活和模塊化,而且可與Express、Sails等Web框架無縫集成。Passport功能單一,即只能作登陸驗證,但很是強大,支持本地帳號驗證和第三方帳號登陸驗證(OAuth和OpenID等),支持大多數Web網站和服務。ios
想了解更多關於 Passport 的,請查看個人另一篇文章: 全棧之初識 Passport & Passport-jwt – Web安全的守護神git
本文中只涉及最基本最經常使用的 本地帳號驗證
生成express項目web
npm install express-generator -g express -e --git RMS-BE
安裝好以上說的各類依賴後,整理項目結構redis
RMS-BE |----node_modules |----src |----common # 公共js (配置文件/二次封裝) |----passport-local.js # passport local 策略 |----config # 數據庫配置模塊 |----index.js |----controllers # MVC中的C,用戶數據與視圖的銜接處理 |----auth.js # 登陸、退出等權限控制 |----people.js |----middleware # 中間件 |----auth.js # token鑑權中間件 |----models # 處理響應的數據,是數據模型 |----user.js |----people.js |----routes # 路由模塊 |----authRouter.js # 登陸等權限控制路由 |----people.js |----server.js # 入口文件 |----package.json
==一、server.js==mongodb
const express = require('express'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const session = require('express-session'); const passport = require('passport'); const config = require('./src/config'); const port = process.env.PORT || 8899; const app = express(); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(session({ secret: config.secret, resave: true, saveUninitialized: true })); app.use(passport.initialize()); // 使 passport 持久化,不僅是session app.use(passport.session()); app.use((req, res, next) => { req.passport = passport // 爲了在中間件中能夠調用到 passport res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); if (req.method == 'OPTIONS') { return res.send(200); } else { next(); } }); const mongoHost = `mongodb://${config.host}:${config.port || 27017}/${config.database}` mongoose.Promise = global.Promise mongoose.connect(mongoHost, { useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, config: { autoIndex: true, }, }).then(() => { console.log('connection established:', mongoHost) }).catch(err => { console.error(err) }) require('./src/common/passport-local')(passport); // Route Section require('./src/routes/authRouter')(app); require('./src/routes/people')(app); app.listen(port, () => console.log(`Server running on PORT: ${port}`));
==二、routes/authRouter.js==vuex
const authMiddleware = require('../middleware/auth') const authController = require('../controllers/auth') module.exports = function (app) { app.post('/login', authController.login) app.post('/validateToken', authMiddleware, authController.validateToken) app.get('/getUser', authMiddleware, authController.getUser) app.post('/register', authMiddleware, authController.register) app.post('/changeUserInfo', authMiddleware, authController.changeUserInfo) app.post('/deleteUser', authMiddleware, authController.deleteUser) app.post('/logout', authMiddleware, authController.logout) }
==三、models/user.js==
const mongoose = require('mongoose') const moment = require('moment') const uuid = require('uuid') const Schema = mongoose.Schema const userSchema = new Schema({ user_id : { type: Number, required: true }, user_uuid : { type: String, required: true }, user_name : { type: String, required: true }, user_password : { type: String, required: true }, user_created : { type: Number }, user_updated : { type: Number }, user_role : { type: Number, required: true } // 0: 帳戶鎖定(無權限) 1: 普通用戶 2: admin 3: superadmin }) userSchema.pre('validate', function (next) { this.user_uuid = this.user_uuid || uuid.v4() this.user_created = this.user_created || moment().format('X') this.user_updated = moment().format('X') next() }) module.exports = mongoose.model('user', userSchema)
==四、middleware/auth.js==
module.exports = function (req, res, next) { // if (req.isAuthenticated()) return next() req.passport && req.passport.authenticate('jwt', { session: false }, (err, user, info) => { if (err) { return next(err) } if (!user) return res.send({ success: true, code: 0, message: '權限禁止' }) req.userInfo = user next() })(req, res, next) }
==五、controllers/auth.js==
const User = require('../models/user') const md5 = require('md5') // const bcrypt = require('bcrypt') const { get } = require('lodash') const uuid = require('uuid') const jwt = require('jsonwebtoken') //token 認證 const config = require('../config') // const salt = bcrypt.genSaltSync(config.saltRounds) const GenerateToken = user => { return jwt.sign({ user_id: get(user, 'user_id'), user_uuid: get(user, 'user_uuid'), user_name: get(user, 'user_name'), user_role: get(user, 'user_role') }, config.JWT_SECRET, { jwtid: uuid.v4(), expiresIn: config.JWT_EXPIRY, issuer: config.JWT_ISSUER, audience: config.JWT_AUDIENCE, algorithm: config.JWT_ALG }) } const ReturnUserInfo = user => { return { user_id: get(user, 'user_id'), user_uuid: get(user, 'user_uuid'), user_name: get(user, 'user_name'), user_created: get(user, 'user_created'), user_updated: get(user, 'user_updated'), user_role: get(user, 'user_role') } } const login = async function (req, res, next) { try { const username = get(req, 'body.user_name') const password = get(req, 'body.user_password') const userInfo = await User.findOne({ user_name: username }) if (userInfo) { const verify = md5(password) === get(userInfo, 'user_password') if (verify) { // 生成token const token = GenerateToken(userInfo) res.cookie('authorization', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', expires: new Date(Date.now() + config.JWT_EXPIRY) }) // 存儲token到redis return res.send({ success: true, code: 1, token: 'Bearer ' + token, user: ReturnUserInfo(userInfo) }) } else { return res.send({ success: true, code: 0, message: '密碼錯誤!' }) } } else { return res.send({ success: true, code: 0, message: '該用戶不存在!' }) } } catch (error) { return res.send({ success: true, code: 0, message: '登陸失敗!error:' + error }) } } const validateToken = async function (req, res, next) { const token = GenerateToken(req.userInfo) res.cookie('authorization', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', expires: new Date(Date.now() + config.JWT_EXPIRY) }) return res.send({ success: true, code: 1, user: ReturnUserInfo(req.userInfo) }) } const getUser = async function (req, res, next) { if (get(req, 'userInfo.user_role') > 2) { try { const allUser = await User.find({}) return res.send({ success: true, code: 1, user_list: allUser || [] }) } catch (error) { return res.send({ success: true, code: 0, message: '獲取用戶列表失敗!error:' + error }) } } else { return res.send({ success: true, code: 0, message: '權限不足,禁止訪問!' }) } } const register = async function (req, res, next) { if (get(req, 'userInfo.user_role') === 3) { try { const username = get(req, 'body.user_name') const password = get(req, 'body.user_password') || '123456' const userRole = get(req, 'body.user_role') const checkUsername = await User.findOne({ user_name: username }) if (checkUsername) { return res.send({ success: true, code: 0, message: '該用戶已存在!' }) } else { let userid = 0 const rows = await User.find({}).sort({'user_id':-1}).limit(1) if (rows && rows.length) { userid = rows[0].user_id + 1 } else { userid = 0 } const newUserInfo = new User({ user_id: userid, user_name: username, user_password: md5(password), user_role: userRole }) newUserInfo.save().then(result => { return res.send({ success: true, code: 1, user: ReturnUserInfo(result) }) }).catch(error => { console.log("Error:" + error) return res.send({ success: true, code: 0, message: '註冊失敗!error:' + error }) }) } } catch (error) { return res.send({ success: true, code: 0, message: '註冊失敗!error:' + error }) } } else { return res.send({ success: true, code: 0, message: '權限不足,禁止註冊!' }) } } const changeUserInfo = async function (req, res, next) { const useruuid = get(req, 'body.user_uuid') const tokenUserRole = get(req, 'userInfo.user_role') if (tokenUserRole === 3 || useruuid === get(req, 'userInfo.user_uuid')) { try { const isResetPsw = get(req, 'body.is_reset_password') const isChangePsw = get(req, 'body.is_change_password') const isChangeUsername = get(req, 'body.is_change_username') const username = get(req, 'body.user_name') const userRole = tokenUserRole === 3 ? get(req, 'body.user_role') : tokenUserRole const userInfo = await User.findOne({ user_uuid: useruuid }) if (userInfo) { const params = { user_name: username, user_role: userRole } if (isChangeUsername) { // 重置用戶名 const checkNewUsername = await User.findOne({ user_name: username }) if (checkNewUsername) return res.send({ success: true, code: 0, message: '用戶名已存在' }) params.user_name = username } if (isResetPsw) { // 重置密碼 params.user_password = md5('123456') } if (isChangePsw) { // 經過原密碼修改密碼 const password = get(req, 'body.user_password') const verify = md5(password) === get(userInfo, 'user_password') if (!verify) return res.send({ success: true, code: 0, message: '原密碼錯誤!' }) const newPassword = get(req, 'body.new_user_password') params.user_password = md5(newPassword) } await User.update({ user_uuid: useruuid }, params) const newUserInfo = await User.findOne({ user_uuid: useruuid }) return res.send({ success: true, code: 1, user: ReturnUserInfo(newUserInfo) }) } else { return res.send({ success: true, code: 0, message: '該用戶不存在!' }) } } catch (error) { return res.send({ success: true, code: 0, message: '修改失敗!error:' + error }) } } else { return res.send({ success: true, code: 0, message: '權限不足,禁止修改!' }) } } const deleteUser = async function (req, res, next) { if (get(req, 'userInfo.user_role') === 3) { try { const useruuid = get(req, 'body.user_uuid') const checkUser = await User.findOne({ user_uuid: useruuid }) if (checkUser) { const newUserInfo = await User.remove({ user_uuid: useruuid }) return res.send({ success: true, code: 1, user: ReturnUserInfo(newUserInfo) }) } else { return res.send({ success: true, code: 0, message: '該用戶不存在!' }) } } catch (error) { return res.send({ success: true, code: 0, message: '刪除失敗!error:' + error }) } } else { return res.send({ success: true, code: 0, message: '權限不足,禁止刪除!' }) } } const logout = async function (req, res, next) { // 清除redis中的token res.clearCookie('authorization') return res.send({ success: true, code: 1, message: '退出成功!' }) } module.exports = { login, validateToken, getUser, register, changeUserInfo, deleteUser, logout }
==六、config/index.js==
module.exports = { secret : 'renyide', host : process.env.DB_HOST || 'localhost', port : process.env.DB_PORT || '27017', database: 'rms', JWT_SECRET: 'renyide', JWT_EXPIRY: 86400000, JWT_ISSUER: 'RMS', JWT_AUDIENCE: 'RMS_XH', JWT_ALG: 'HS256' }
==七、common/passport-local.js==
const JwtStrategy = require('passport-jwt').Strategy const ExtractJwt = require('passport-jwt').ExtractJwt const User = require('../models/user') const config = require('../config') const opts = { // Prepare the extractor from the header. jwtFromRequest: ExtractJwt.fromExtractors([ req => req.cookies['authorization'], ExtractJwt.fromUrlQueryParameter('access_token'), ExtractJwt.fromAuthHeaderWithScheme('Bearer'), ]), // Use the secret passed in which is loaded from the environment. This can be // a certificate (loaded) or a HMAC key. secretOrKey: config.JWT_SECRET, // Verify the issuer. issuer: config.JWT_ISSUER, // Verify the audience. audience: config.JWT_AUDIENCE, // Enable only the HS256 algorithm. algorithms: [config.JWT_ALG], // Pass the request object back to the callback so we can attach the JWT to it. passReqToCallback: true } module.exports = passport => { passport.use(new JwtStrategy(opts, async function (req, jwt_payload, done) { try { const userInfo = await User.findOne({ user_uuid: jwt_payload.user_uuid }) if (userInfo && userInfo.user_role > 0) { done(null, userInfo) } else { done(null, false) } } catch (e) { return done(e) } })) }
==一、中間件==
export default async function ({ app, store, error, redirect, req }) { if (req && (req.url === '/__webpack_hmr' || req.url === '/__webpack_hmr/client' || req.url === '/api/v1/validateToken')) return await store.dispatch('auth/validateToken') if (!store.state.auth.user.user_id) { return redirect('/login') } else if (store.state.auth.user.user_role === 0) { alert('您的帳戶被凍結,請聯繫管理員!') return redirect('/login') } }
==二、以vuex中是否存在userid判斷是否有token,登陸校驗請求==
import Vue from 'vue' import Vuex from 'vuex' import { deleteCookie } from '~/utils/cache' Vue.use(Vuex) export const state = () => ({ user: {} }) export const mutations = { setUser (state, data) { state.user = data } } export const actions = { async login ({ commit, dispatch }, params) { try { const { data } = await this.$axios.post(`/api/v1/login`, params) if (data.code !== 0) { this.$message.success('登錄成功!') commit('setUser', data.user) } else { console.log(data.message) this.$message.error('登陸失敗!' + data.message) } } catch (e) { console.log(e) await dispatch('logout') throw e } }, async logout ({ commit }) { try { const { data } = await this.$axios.post(`/api/v1/logout`) if (data.code !== 0) { this.$message.success('退出成功!') } else { console.log(data.message) this.$message.error('退出失敗!' + data.message) } } catch (e) { console.log(e) } finally { commit('setUser', {}) deleteCookie('authorization') window.location.href = '/login' } }, async validateToken ({ commit, dispatch }) { try { const { data } = await this.$axios.post(`/api/v1/validateToken`) if (data.code === 0) { console.log(data.message) this.$message.error(data.message) } else { commit('setUser', data.user) } } catch (e) { console.log(e) } } }
本文由博客一文多發平臺 OpenWrite 發佈!