<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] >[success] # 登陸/登出以及JWT認證 在本章中將使用一個 **后端的真實服務** ,來結合這個服務做 **前端應用** 的 **登陸** 以及 **登出** ,并且我們使用 **JWT(全稱:Json Web Token)** 來 **進行認證** 。 >[success] ## 后端代碼概覽 這里使用 **[express搭建](http://www.hmoore.net/wangjiachong/code/1272384)** 了一個服務,這個 **服務運行在本地的 3000端口** ,大家想看可以直接進行 **代碼下載** ,下載 **現成的代碼** ,進行運行,下面主要看一下幾個主要用到的 **后端接口** 。 **本地服務代碼下載地址** : 鏈接:https://pan.baidu.com/s/1LcKTiDsoZy_RmEjmfJuKIw 提取碼:cdaq **運行服務執行指令** : ~~~ npm start ~~~ 1. **serve/routes/index.js** **login登陸接口** ~~~ var express = require('express'); var router = express.Router(); const jwt = require('jsonwebtoken') // 模擬從數據庫獲取用戶信息 const getPasswordByName = (name) => { return { password: '123' } } router.post('/getUserInfo', function(req, res, next) { res.status(200).send({ code: 200, data: { name: 'Lison' } }) }); // 登陸接口 router.post('/login', function(req, res, next) { // 獲取前端傳過來的userName跟password const { userName, password } = req.body // 如果有用戶名 if (userName) { // 獲取用戶信息(如果有密碼通過這個方法去數據庫查詢該用戶信息,如果沒有密碼,用戶信息就返回一個空字符串) const userInfo = password ? getPasswordByName(userName) : '' // 【用戶信息為空】 或者 【密碼為空】 或者 【輸入密碼與用戶信息密碼不對】3項中1項不對就拋出錯誤 if (!userInfo || !password || userInfo.password !== password) { res.status(401).send({ // 拋出錯誤:用戶名或密碼不對 code: 401, mes: 'user name or password is wrong', data: {} }) } else { // 成功 res.send({ // 給前端返回一個token,token是通過一個jwt的一個庫來生成的 code: 200, mes: 'success', data: { // JWT生成規則: 可以自己來定義規則 // 參數1:是一個對象傳入的是 【用戶名稱】 // 參數2:我們用來加密的一個自定義的一個字符串,這里可以定義我們的密鑰,這里隨便寫一個abcd // 參數3:我們可以在里面設置一些信息,這里設置了一個【token過期時間】 token: jwt.sign({ name: userName }, 'abcd', { expiresIn: '1d' // 1d?=?1天?10000?=?10秒 }) } }) } } else { // 如果無用戶名 res.status(401).send({ // 拋出錯誤:用戶名為空 code: 401, mes: 'user name is empty', data: {} }) } }); module.exports = router; ~~~ 2. **serve/routes/users.js** **授權接口** ~~~ var express = require('express'); var router = express.Router(); const jwt = require('jsonwebtoken') /* GET users listing. */ router.get('/', function(req, res, next) { res.send('respond with a resource'); }); router.get('/getUserInfo', function(req, res, next) { res.send('success') }) // 授權接口 router.get('/authorization', (req, res, next) => { // req.userName從app.js中中間件(攔截器)的token都正確才給添加的用戶名稱 const userName = req.userName res.send({ code: 200, mes: 'success', data: { token: jwt.sign({ name: userName }, 'abcd', { expiresIn: '1d' // 1d?=?1天?10000?=?10秒 }) } }) }) module.exports = router; ~~~ 3. **serve/app.js(中間件:類似攔截器)** **應用核心配置文件** ~~~ var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); const jwt = require('jsonwebtoken') var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); // app.all('*', (req, res, next) => { // res.header('Access-Control-Allow-Origin', '*') // res.header('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type') // res.header('Access-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS') // next() // }) // 白名單(不需要做token校驗的接口) const whiteListUrl = { get: [ ], post: [ '/index/login', ] } // 判斷請求是否在白名單中的方法 const hasOneOf = (str, arr) => { return arr.some(item => item.includes(str)) } // 中間件(類似攔截器,每次請求時候會走這里) app.all('*', (req, res, next) => { // 獲取當前請求方式并且轉換成小寫(post 或者 get等等) let method = req.method.toLowerCase() // 獲取當前請求的路徑 let path = req.path // 有一些接口是不需要token校驗的,所以在這里設置一下白名單, // 如果請求方式在白名單里面的對應上,并且請求方式里有請求的這個地址就返回true,接口正常執行,不需要token if(whiteListUrl[method] && hasOneOf(path, whiteListUrl[method])) next() // 白名單走這里 else { // 白名單之外的都需要token校驗 // 在請求的header中取出authorization const token = req.headers.authorization // 判斷沒有token就拋出沒有token請登錄 if (!token) res.status(401).send('there is no token, please login') else { // 有token就判斷使用JWT提供的方法,校驗token是否正確 // 第一個參數把獲取到的token傳入 // 第二個參數是我們生成token時候傳入的密鑰,當然這個密鑰可以抽離出一個文件每次從文件中獲取密鑰 // 第三個參數是一個回調函數,第一個參數是錯誤信息,第二個參數是從token中解碼出來的信息 jwt.verify(token, 'abcd', (error, decode) => { if (error) res.send({ // token錯誤 code: 401, mes: 'token error', data: {} }) else { // token正確返回用戶名,繼續往下走 req.userName = decode.name next() } }) } } }) app.use('/index', indexRouter); app.use('/users', usersRouter); app.use('/data', dataRouter); // catch 404 and forward to error handler app.use(function(err, req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app; ~~~ >[success] ## 登陸以及Token處理 具體操作如下: >[success] ### 安裝所需依賴 首先 **安裝2個模塊** ,**js-cookie(它是對cookie進行一些操作,可以設置、讀取cookie)** 、**md5(可以對字符串進行md5加密,用在登陸時候提交密碼時候加密密碼)** ,然后我們依次 **執行安裝指令** ,進行安裝 **js-cookie 安裝指令** ~~~ npm install js-cookie --save ~~~ **md5 安裝指令** ~~~ npm install md5 ~~~ >[success] ### 登陸頁面邏輯 ![](https://img.kancloud.cn/f3/49/f34999ee35fa12ddb6d7e3dc0a9a75f9_688x454.png) ![](https://img.kancloud.cn/da/ce/dacee54a1aac4c31c64f2272951bf296_2169x235.jpg) 1. **登陸頁面代碼以及邏輯** 在 **login.vue(登陸頁面)** 里面寫好一個 **簡單的表單** ,大概是下圖這個樣子 ![](https://img.kancloud.cn/a7/40/a740bc7ecab76ad10a982e5fdebc8255_436x43.png) **點擊登陸按鈕** 后執行 **vuex** 中的 **actions** 的 **login異步方法** **src/views/login.vue** ~~~ <template> <div> <input type="text" placeholder="請輸入賬號" v-model="userName" /> <input type="password" placeholder="請輸入密碼" v-model="password" /> <button @click="handleSubmit">登陸</button> </div> </template> <script> import { mapActions } from 'vuex' export default { name: 'login_page', data(){ return { userName: 'Lison', // 用戶名 password: '123' // 密碼 } }, methods: { // 引入action中的login方法 ...mapActions([ 'login' ]), // 登陸 handleSubmit(){ this.login({ userName: this.userName, password: this.password }).then(() => { console.log('成功') this.$router.push({ name: 'home' }) }).catch(error => { console.log(error) }) } } } </script> <style> </style> ~~~ 2. **vuex中的代碼展示** **點擊登陸時候** 調用 **vuex** 中 **actions** 里定義的 **login方法**,這個 **login方法** 中 **調用了后端的登陸接口** **src/store/module/user.js** ~~~ // 接口引入 import { login, authorization } from '@/api/user' // 引入業務類方法 import { setToken } from '@/lib/util' const state = {} const mutations = {} const actions = { /** * 登陸方法 * @param commit 執行一個mutation方法來修改state * @param params this.$store.dispatch('login', '我是params')時傳的參數 */ login({ commit }, { userName, password }){ return new Promise((resolve, reject) => { // 登陸接口 login({ userName, password }).then(res => { if(res.code === 200 && res.data.token){ setToken(res.data.token) resolve() } else { reject(new Error('錯誤')) } }).catch(error => { reject(error) }) }) }, /** * 校驗token是否失效 * @param commit 執行一個mutation方法來修改state * @param token token信息 */ authorization({ commit }, token){ return new Promise((resolve, reject) => { authorization().then(res => { if(parseInt(res.code) === 401){ reject(new Error('token error')) } else { resolve() } }).catch(error => { reject(error) }) }) } } export default { state, mutations, actions } ~~~ 3. **接口文件** **src/api/user.js** ~~~ import axios from './index' // 登陸接口 export const login = ({ userName, password }) => { return axios.request({ url: '/index/login', method: 'post', data: { userName, password } }) } // 檢驗token是否有效 export const authorization = () => { return axios.request({ url:'/users/authorization', method: 'get' // 這行可以不寫,默認是get }) } ~~~ **接口成功** 后, **后端會返回一個 token** ,我們需要把 **token** 儲存起來,在 **后續接口調用時,將它添加到我們請求的header中,傳給后端,后端拿到 token 進行驗證**,此時需要用到剛剛下載的 **js-cookie** 在 **util.js** 中 **封裝一個儲存 token 的業務類方法** ,代碼如下: **src/lib/util.js** ~~~ import Cookie from 'js-cookie' // 設置title export const setTitle = (title) => { window.document.title = title || 'admin' // 默認title } /** * 設置token * @param {string} token - 登陸成功后,返回的token * @param {string} tokenName - 儲存到Cookie時的token名字 */ export const setToken = (token, tokenName = 'token') => { Cookie.set(tokenName, token) } /** * 獲取token * @param {string} tokenName - 儲存到Cookie時的token名字 */ export const getToken = (tokenName = 'token') => { return Cookie.get(tokenName) } ~~~ 4. **路由配置** **login接口成功返回token** 后 **需要跳轉到首頁** ,此時需要在 **路由攔截器中做判斷處理** ,如果有 **token** 會調用一個接口,進行 **token是否可用的校驗 ,如果校驗成功跳轉到首頁** ,**如果沒有 token 證明未登錄,跳轉到登錄頁** **src/router/index.js** ~~~ import Vue from 'vue' import Router from 'vue-router' import routes from './router' import store from '@/store' import { setTitle, setToken, getToken } from '@/lib/util' // 注冊路由 Vue.use(Router) // vue-router實例 const router = new Router({ routes }) // 注冊全局前置守衛 router.beforeEach((to, from, next) => { // 動態設置title to.meta && setTitle(to.meta.title) // 獲取token const token = getToken() if(token){ // 已登錄 // 調用接口判斷token是否失效 store.dispatch('authorization', token).then(() => { // token驗證成功 // 如果跳轉的頁面為登陸頁,就強制跳轉到首頁 if(to.name === 'login') next({ name: 'home' }) else next() }).catch(error => { // token驗證錯誤 // 這里需要清空token,再返回登錄頁,不然會陷入死循環,回到登錄頁還是有token,token失效還是回到登錄頁,如此反復 setToken('') // 這里也可以使用js-cookie提供的clear方法 next({ name: 'login' }) }) } else { // 未登錄 // 如果去的頁面是登陸頁,直接跳到登陸頁 if(to.name === 'login') next() // 如果不是登陸頁,強行跳轉到登陸頁 else next({ name: 'login' }) } }) export default router ~~~ 5. **axios請求攔截器添加token** 因為在調用 **authorization 校驗接口** 時,需要 **參數 token** , **只需要添加到請求攔截器** 中即可,這樣后續接口的請求 **token都會在請求頭上添加** 。 **src/lib/axios.js** ~~~ import axios from 'axios' import { baseURL } from '@/config' import { getToken } from '@/lib/util' class HttpRequest { constructor(baseUrl = baseURL){ // baseUrl = baseURL 是ES6的默認值寫法等同于 baseUrl = baseUrl || baseURL this.baseUrl = baseUrl // this指向創建的實例,當你使用new HttpRequest創建實例時候,它會把this中定義的變量返回給你 this.queue = {} // 創建隊列,每次請求都會向里面添加一個key:value,請求成功后就會去掉這個key:value,直到this.queue中沒有屬性值時,loading關閉 } /** * 默認options配置 */ getInsideConfig(){ const config = { baseURL: this.baseUrl, headers: { // } } return config } distroy (url) { delete this.queue[url] if (!Object.keys(this.queue).length) { // Spin.hide() } } /** * 攔截器 * @param {Object} instance - 通過axios創建的實例 * @param {String} url - 接口地址 */ interceptors(instance, url){ /** * 請求攔截器 * @param {Function} config - 請求前的控制 * @param {Function} error - 出現錯誤的時候會提供一個錯誤信息 */ instance.interceptors.request.use(config => { // 添加全局的Lodaing... if(!Object.keys(this.queue).length){ // Spin.show() } this.queue[url] = true // 每次請求都會把token加到請求頭中 config.headers['Authorization'] = getToken() return config }, error => { return Promise.reject(error) }) /** * 響應攔截器 * @param {Function} res - 服務端返回的東西 * @param {Function} error - 出現錯誤的時候會提供一個錯誤信息 */ instance.interceptors.response.use(res => { this.distroy(url) // 關閉全局的Lodaing... const { data } = res return data }, error => { this.distroy(url) // 關閉全局的Lodaing... return Promise.reject(error.response.data) }) } request(options){ const instance = axios.create() options = Object.assign(this.getInsideConfig(), options) // Object.assign會將2個對象合并成1個對象,相同屬性值會被后者覆蓋 this.interceptors(instance, options.url) // 攔截器 return instance(options) } } export default HttpRequest ~~~ >[success] ### 注釋掉mock.js 把 **main.js** 中引入的 **mock.js** 的引入 **注釋掉** ,因為我們要 **請求真實的接口數據** ,如果這個地方引入了 **mock.js** ,它會用 **mock** 對你的 **所有請求行為進行攔截** ,所以需要 **注釋掉**,代碼如下: **src/main.js** ~~~ import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import './plugins/element.js' import Bus from './lib/bus' // 非生產環境時引入 mock // if(process.env.NODE_ENV !== 'production') require('./mock') Vue.config.productionTip = false Vue.prototype.$bus = Bus new Vue({ router, store, render: h => h(App) }).$mount('#app') ~~~ >[success] ### 配置代理 因為 **express** 起的 **后端服務URL** 是 **http://localhost:3000/** ,而 **前端服務URL** 是 **http://localhost:8080/** ,兩者的 **端口號不同(后端端口:3000,前端端口:8080)** ,所以調用接口時候會 **產生跨域** ,這時候在 **后端沒有添加header頭** , **前端解決跨域需要配置代理**,首先在 **vue.config.js** 中把 **devServer 中 proxy** 修改成 **要代理的后端服務地址** 如下: **vue.config.js** ~~~ const path = require('path') // 引入nodejs的path模塊 const resolve = dir => path.join(__dirname, dir) // resolve方法用來加載路徑 const BASE_URL = process.env.NODE_ENV === 'production' ? '/iview-admin/' : '/' // 判斷當前為開發環境還是打包環境, '/'意思是代表指定在域名的根目錄下,如果要指定到iview-admin下就這樣寫'/iview-admin/', production為生產壞境,development為開發環境 module.exports = { lintOnSave: false, // 取消每次保存時都進行一次' ESLint '檢測 publicPath: BASE_URL, // 項目的基本路徑,vuecli2.0時打包經常靜態文件找不到,就是需要配置這個屬性為'./' chainWebpack: config => { // 配置Webpack config.resolve.alias .set('@', resolve('src')) // 引入文件時候“ @ ”符號就代表src .set('_c', resolve('src/components')) // 引入組件文件夾中的文件就可以用“ _c ”代替src/components }, productionSourceMap: false, // 打包時不生成.map文件,會減少打包體積,同時加快打包速度 devServer: { // 跨域有2種解決方案: 1. 在后端的header中配置, 2. 使用devServer來配置代理解決跨域 proxy: 'http://localhost:3000/' // 這里寫需要代理的URL,這里會告訴開發服務器,將任何未知請求匹配不到靜態文件的請求,都代理到這個URL來滿足跨域 } } ~~~ 然后修改一下 **axios配置文件** 中的 **baseURL** 判斷邏輯,如果是 **開發環境 baseURL 就設置為空字符串** ,因為 **不設置為空字符串,baseURL 不會被代理里配置的URL更改**。 **src/config/index.js** ~~~ // 如果當前是生產環境用生產環境地址,如果是開發環境并且在vue.config.js中配置了代理,就用空字符串【''】,如果未配置代理就用開發環境地址 export const baseURL = process.env.NODE_ENV === 'production' ? 'http://production.com' : '' ~~~ **注意:如果 修改了webpack 的配置 ,必須要 重啟前端的服務,才會生效 。** >[success] ## Token過期處理 一個網站如果 **長時間沒有操作** ,不可能讓它 **登陸完一次,就一輩子不用再登陸了,就一直能用** ,我們會 **設置一個token的過期時間 ,過期之后需要重新登錄** 。例如:用戶 **登陸成功** 后獲取到了 **token** ,**token 過期時間為一天** ,在這一天都在頻繁的使用該網站,到一天了 **token過期** 就 **讓用戶跳出去重新登錄** ,這樣 **用戶體驗不好** ,我們希望 **當用戶長時間使用網站時,我們應該給 token 續命,給它延長使用時間** ,每次 **頁面的跳轉** 都會調用 **authorization接口** ,**authorization接口** 每次都會返回一個 **新的token** ,它會 **重新計時**。 **src/store/module/user.js** ~~~ // 接口引入 import { authorization } from '@/api/user' // 引入業務類方法 import { setToken } from '@/lib/util' const state = {} const mutations = {} const actions = { /** * 校驗token是否失效 * @param commit 執行一個mutation方法來修改state * @param token token信息 */ authorization({ commit }, token){ return new Promise((resolve, reject) => { authorization().then(res => { if(parseInt(res.code) === 401){ reject(new Error('token error')) } else { setToken(res.data.token) // 重新設置token resolve() } }).catch(error => { reject(error) }) }) } } export default { state, mutations, actions } ~~~ >[success] ## 退出登陸 在 **首頁** 寫個 **退出按鈕** ,**點擊按鈕** 時候執行 **setToken('')** 把 **cookie清空** ,但是為了 **保持代碼的一致性** ,還是把這個 **登出方法** 寫在 **vuex** 中進行調用, **清空cookie** 后跳轉到 **登錄頁面** 即可。 1. **首頁代碼** **src/views/Home.vue** ~~~ <template> <div> <h1>首頁</h1> <button @click="handleLogout">退出登錄</button> </div> </template> <script> import { mapActions } from 'vuex' export default { methods: { // 引入vuex中的logout方法 ...mapActions([ 'logout' ]), // 退出登錄 handleLogout(){ this.logout() this.$router.push({ name: 'login' }) } } } </script> ~~~ 2. **vuex模塊中代碼** **src/store/module/user.js** ~~~ // 接口引入 import { login, authorization } from '@/api/user' // 引入業務類方法 import { setToken } from '@/lib/util' const state = {} const mutations = {} const actions = { /** * 登陸方法 * @param commit 執行一個mutation方法來修改state * @param params this.$store.dispatch('login', '我是params')時傳的參數 */ login({ commit }, { userName, password }){ return new Promise((resolve, reject) => { // 登陸接口 login({ userName, password }).then(res => { if(res.code === 200 && res.data.token){ setToken(res.data.token) resolve() } else { reject(new Error('錯誤')) } }).catch(error => { reject(error) }) }) }, /** * 校驗token是否失效 * @param commit 執行一個mutation方法來修改state * @param token token信息 */ authorization({ commit }, token){ return new Promise((resolve, reject) => { authorization().then(res => { if(parseInt(res.code) === 401){ reject(new Error('token error')) } else { resolve() } }).catch(error => { reject(error) }) }) }, /** * 登出方法 */ logout(){ setToken('') } } export default { state, mutations, actions } ~~~ >[success] ## 總結 1. **安全性要求不高** :上面的方案適合用于 **安全性要求不高的情況** 。 2. **安全性要求高** :如果你的系統 **對安全性要求比較高** ,就不能使用上面的方案, **不能使用 js 取到 token 存入到 cookie 中,不能進行一些判斷 cookie 里的 token 的邏輯** ,需要 **在服務端設置一個開啟 httpOnly,httpOnly設置為 true 后,就只能通過服務端來把 token 設置到 cookie 中了,無法通過 js 腳本來讀取操作這個 cookie了,這樣就能避免一些跨站腳本攻擊。**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看