Vue學(xué)習(xí)筆記-攔截器

  • 攔截器: 對(duì)特定的http請(qǐng)求或響應(yīng)消息或請(qǐng)求頭進(jìn)行驗(yàn)證,攔截不合法的http交互以保證web環(huán)境的安全。
  • 攔截器起一個(gè)攔截作用攔,在請(qǐng)求接口時(shí),多一次或多次驗(yàn)證。例如:你寫(xiě)了幾個(gè)請(qǐng)求數(shù)據(jù)的接口,開(kāi)啟服務(wù)后,用戶沒(méi)登錄直接訪問(wèn)這些接口,也是可以拿到數(shù)據(jù)的,但這就違背了后臺(tái)管理系統(tǒng)必須先登錄的原則,一些不良用心的人就會(huì)利用這個(gè)漏洞來(lái)竊取你的數(shù)據(jù)庫(kù)數(shù)據(jù)了。這時(shí)就需要到攔截器了。
  • 前后臺(tái)交互一定要遵循一個(gè)原則:互不信任原則。前端發(fā)送到后臺(tái)的參數(shù)(必須在前端驗(yàn)證合法的才能發(fā)送),后臺(tái)必須驗(yàn)證是否合法(是否符合該參數(shù)的原定數(shù)據(jù)類型和值范圍),后臺(tái)返回給前端的數(shù)據(jù),也必須驗(yàn)證是否為約定的數(shù)據(jù)結(jié)構(gòu)和值類型。

攔截器原理和實(shí)現(xiàn)

  • 這里引用第三方的ajax庫(kù) –> axios
  • axios: 基于ES6新語(yǔ)法promise的一個(gè)前端ajax庫(kù)
    // http請(qǐng)求攔截
    axios.interceptors.request.use(config => {
        if (config.method == 'post' && config.url != '/login') {
            config.data = {
                ...config.data,
                ...{"user": "admin"},
                ...{ "datetime": new Date() }
            }
        } else if (config.method == 'get') {
            if (/\?/.test(config.url)) {
                config.url += 'user=admin&datetime=' + new Date();
            } else {
                config.url += '?user=admin&datetime=' + new Date();
            }
        }
        return config;
    });
    // http響應(yīng)攔截
    axios.interceptors.response.use(response => {
        switch (response.data.requestIntercept) {
            case 1:
                console.log('登錄信息已失效,請(qǐng)重新登錄!');
        }
        return response;
    });
  • 這個(gè)示例中設(shè)置了前端向服務(wù)端發(fā)起請(qǐng)求時(shí)的http請(qǐng)求攔截和服務(wù)端返回?cái)?shù)據(jù)時(shí)的http響應(yīng)攔截。
  • interceptors是axios的一個(gè)攔截器對(duì)象,axios.interceptors.request是對(duì)http請(qǐng)求攔截配置的對(duì)象,這里我設(shè)置了給每個(gè)請(qǐng)求添加一個(gè)系統(tǒng)當(dāng)前的時(shí)間和一個(gè)用戶名(實(shí)際項(xiàng)目中添加用戶名變量),這樣可以避免get請(qǐng)求出現(xiàn)304,并且每次發(fā)起請(qǐng)求都向服務(wù)器發(fā)送一次用戶名。
  • axios.interceptors.response是對(duì)http響應(yīng)攔截配置的對(duì)象,這里如果服務(wù)器返回json為{requestIntercept: 1},則判定服務(wù)器拒絕了頁(yè)面的http請(qǐng)求的執(zhí)行,直接返回一個(gè)狀態(tài)提示,否則就返回正常的 response。
  • 前端這樣設(shè)置了攔截器就一勞永逸了嗎?當(dāng)然不是的,前端的永遠(yuǎn)不是安全的

安全級(jí)別的攔截器 —- nodejs服務(wù)端的攔截器原理和實(shí)現(xiàn)

  • nodejs: 基于谷歌V8引擎,使用javascript編程實(shí)現(xiàn)的一個(gè)web服務(wù)端編程語(yǔ)言
  • 服務(wù)端的攔截器才是安全的,先看下面這段簡(jiǎn)單的攔截器代碼,主要攔截的是沒(méi)有用戶名或有用戶名但在服務(wù)端沒(méi)有對(duì)應(yīng)的session的http請(qǐng)求
    // 攔截器
    app.all('/*', function (req, res, next) {
        if (req.url == '/login') {
            next();
        } else {
            if (req.method == "GET") {
                username = req.query.user;
            } else if (req.method == "POST") {
                username = req.body.user;
            }
            if (sessionPool[username] && getSid(res.req.headers.cookie) == sessionPool[username]) {
                // 用戶session存在
                next();
            } else {
                res.json({ requestIntercept: 1 });  // 頁(yè)面拿到這個(gè)值在做攔截處理即可
            }
        }
    });
  • app.all(‘/’),這里的app是 express() 對(duì)象,app.all() 是針對(duì)所有的http請(qǐng)求, ‘/’匹配的是所有以“/”開(kāi)頭的http請(qǐng)求,后面執(zhí)行的實(shí)際上相當(dāng)于是一個(gè)接口,三個(gè)參數(shù)分別是request,response,next,其中next是攔截器通過(guò)的回調(diào)函數(shù)
  • 這里的思路就是先判斷請(qǐng)求是否為登錄接口,不是的話就取出請(qǐng)求中的user參數(shù),用這個(gè)user去驗(yàn)證兩邊的cookie是否相同,若不同則直接返回{ requestIntercept: 1 }這個(gè)json,告訴前端驗(yàn)證不通過(guò);驗(yàn)證通過(guò)的調(diào)用next()回調(diào)函數(shù)進(jìn)入下一個(gè)處理環(huán)節(jié) — 數(shù)據(jù)讀取接口

完整的前后端交互攔截器示例:

  • 完整的攔截器設(shè)置:(以 /getlist 接口為例)
    1. 前端發(fā)起/getlist 接口的http請(qǐng)求,攔截并驗(yàn)證請(qǐng)求
    2. 服務(wù)端通過(guò)app.all(‘/*’),匹配到/getlist 接口是 /* 開(kāi)頭的請(qǐng)求,立即攔截/getlist 接口的http請(qǐng)求并驗(yàn)證
    3. 驗(yàn)證通過(guò)后,調(diào)用next()方法將后續(xù)處理交給 app.('/getlist') 處理
    4. app.('/getlist') 處理完成后返回?cái)?shù)據(jù)給前端
    5. 前端驗(yàn)證返回的json數(shù)據(jù)是不是{requestIntercept: 1},若不是則交給 axios.post('/getList').then() 處理,至此一次http請(qǐng)求完成,若返回的json數(shù)據(jù)是{requestIntercept: 1},那么在 axios.interceptors.response 就會(huì)被攔截,同時(shí)會(huì)告知axios.post('/getList') 對(duì)象將promise對(duì)象的狀態(tài)由 pending(promise正在異步執(zhí)行中) 改為 resolved(promise執(zhí)行完畢)
  • 前端: login.vue
 <template>
        <div id="app" class="login-form">
            <input type="username" v-model="user">
            <input type="password" v-model="pwd">
            <input type="button" value="登錄" @click="login">
            <input type="button" value="獲取數(shù)據(jù)" @click="getList">
            <input type="button" value="注銷" @click="logout">
        </div>
</template>
     
        <script type='text/javascript'>
            // http請(qǐng)求攔截
            axios.interceptors.request.use(config => {
                if (config.method == 'post' && config.url != '/login') {
                    config.data = {
                        ...config.data,
                        ...{ "user": "admin" }
                    }
                } else if (config.method == 'get') {
                    if (/\?/.test(config.url)) {
                        config.url += 'user=admin'
                    } else {
                        config.url += '?user=admin'
                    }
                }
                return config;
            });

            // http響應(yīng)攔截
            axios.interceptors.response.use(response => {
                switch (response.data.requestIntercept) {
                    case 1:
                        console.log('登錄信息已失效,請(qǐng)重新登錄!');
                }
                return response;
            });
            let Vm = new Vue({
                el: '#app',
                data() {
                    return {
                        user: 'admin',
                        pwd: 'admin'
                    }
                },
                methods: {
                    login() {
                        const that = this;
                        axios.post('/login', {
                            "user": that.user,
                            "pwd": that.pwd
                        }).then((res) => {
                            console.log(res.data);
                            if (res.data.status == 1) {
                                alert('登陸成功!');
                            }
                        }).catch((err) => {
                            console.log('出錯(cuò)了-,-!', err);
                        })
                    },
                    getList() {
                        axios.post('/getList', {
                            // "user": "admin"
                        }).then((res) => {
                            console.log(res.data);
                        }).catch((err) => {
                            console.log('出錯(cuò)了-,-!', err);
                        })
                    },
                    logout() {
                        axios.post('/logout', {
                            // "user": "admin"
                        }).then((res) => {
                            console.log(res.data);
                            if (res.data.logout == 1) {
                                alert('注銷成功');
                            }
                        }).catch((err) => {
                            console.log('出錯(cuò)了-,-!', err);
                        })
                    }
                }
            })
        </script>
        
        <style>
            * {
                margin: 0;
                padding: 0;
            }
            input {
                -web-kit-appearance: none;
                -moz-appearance: none;
                font-size: 1.4em;
                height: 2em;
                margin: 0.5em 0;
                border-radius: 4px;
                border: 1px solid #c8cccf;
                color: #6a6f77;
                outline: 0;
            }
            input:focus {
                border: 1px solid #ff7496;
            }
            input[type="button"]:focus {
                background-color: #999999;
            }
            .login-form {
                width: 25%;
                margin: 100px auto;
                line-height: 3em;
            }
            .login-form input,
            .login-form button {
                width: 100%;
            }
        </style>
  • 服務(wù)端:app.js
    const express = require('express');
    const bodyParser = require('body-parser');
    const fs = require('fs');
    const path = require('path');
    const mysql = require('mysql');

    const app = express();
    app.use(express.static(path.resolve(__dirname, './www')));  // 默認(rèn)首頁(yè)為www下的index.html
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extend: true }));

    const sessionPool = {};

    const pool = mysql.createPool({
        host: 'mysql數(shù)據(jù)庫(kù)IP',
        user: 'mysql連接用戶,最高權(quán)限用戶為root',
        password: '填寫(xiě)你的密碼',
        port: '數(shù)據(jù)庫(kù)端口,默認(rèn)3306',
        database: '使用的mysql數(shù)據(jù)庫(kù)名',
        multipleStatements: true
    });

    // 攔截器
    app.all('/*', function (req, res, next) {
        let url = req.url;
        if (url == '/login') {
            next();
        } else {
            if (req.method == "GET") {
                username = req.query.user;
            } else if (req.method == "POST") {
                username = req.body.user;
            }
            if (sessionPool[username] && getSid(res.req.headers.cookie) == sessionPool[username]) {
                // 用戶session存在
                next();
            } else {
                res.json({ requestIntercept: 1 });  // 頁(yè)面拿到這個(gè)值在做攔截處理即可
            }
        }
    });

    // 請(qǐng)求錯(cuò)誤
    app.get('/error', function (req, res) {
        res.send(fs.readFileSync(path.resolve(__dirname, './www/error.html'), 'utf-8'))
    });

    // 測(cè)試接口
    app.get('/', function (req, res) {
        res.json({ test: `測(cè)試服務(wù)器正常!` });
    })

    // 登錄接口
    app.post('/login', function (req, res) {
        // 判斷是否已在線
        if (sessionPool[req.body.user]) {
            // 在線
            delete sessionPool[req.body.user];
        }
        // 使用數(shù)據(jù)庫(kù)連接池
        pool.getConnection(function (err, connection) {
            // 多語(yǔ)句查詢示例
            connection.query("select * from userlist where username = '" + req.body.user + "' and password = '" + req.body.pwd + "' and delMark = '0'; select count(1) from userlist", function (err, rows) {
                if (err) {
                    throw err;
                } else {
                    if (rows[0].length > 0) {
                        // 設(shè)置cookie
                        let cookieSid = req.body.user + Date.parse(new Date());
                        res.setHeader("Set-Cookie", ["sid=" + cookieSid + ";path=/;expires=" + new Date("2030")]);
                        // 先存儲(chǔ)session到sessionPool
                        sessionPool[req.body.user] = cookieSid;
                        // 返回登錄成功的信息
                        res.json({ status: 1, dbData: rows[0], session: req.session });
                        res.end();
                    } else {
                        // 用戶不存在
                        res.json({ status: 0 });
                        res.end();
                    }
                }
            });
            // 釋放本次連接
            connection.release();
        });
    })

    // 退出登錄
    app.post('/logout', function (req, res) {
        delete sessionPool[req.body.user];
        res.json({ logout: 1 });
        res.end();
    })

    app.post('/getList', function (req, res) {
        pool.getConnection(function (err, connection) {
            connection.query('select * from userlist', function (err, rows) {
                if (err) {
                    throw err;
                } else {
                    res.json({ list: rows });
                    res.end();
                }
            });
            connection.release();
        })
        console.log('session池 ', sessionPool);
    });

    app.listen(8000, function () {
        console.log('ssh@git 0.0.0.0:8000 succeed');
    })

    /*
    * 公共方法
    */

    // 解析cookie中的sid
    function getSid(cookieStr) {
        let sid = '', cookieArr = cookieStr.split(';');
        for (let i = 0; i < cookieArr.length; i++) {
            if (cookieArr[i].trim().substring(0, 3) == 'sid') {
                return sid = cookieArr[i].trim().substring(4, cookieArr[i].length);
            }
        }
        return sid;
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容