1. 程式人生 > >nodejs 和 axios 在前後端http互動中的攔截器原理和實現(一)

nodejs 和 axios 在前後端http互動中的攔截器原理和實現(一)

nodejs 和 axios 在http互動中的簡易攔截器(一)

nodejs –> 基於谷歌V8引擎,使用javascript程式設計實現的一個web服務端程式語言
axios –> 基於ES6新語法promise的一個前端ajax庫
攔截器 –> 對特定的http請求或響應訊息或請求頭進行驗證,攔截不合法的http互動以保證web環境的安全

引入

  • 先從我為什麼要寫這個無聊又沒有挑戰的攔截器開始說吧。昨天一同學問了我一個問題:“誒,大哥啊,你那個後臺管理系統demo為什麼要設定攔截器,這個攔截器是幹嘛用的?”
  • 我的回答很簡單,因為這個問題實際上真的很簡單:“攔截器就是在你的請求要做接下來的處理時,多一次或多次驗證。例如:你寫了幾個請求資料的介面,開啟服務後,使用者沒登入直接訪問這些介面,也是可以拿到資料的,但這就違背了後臺管理系統必須先登入的原則,有心人就會利用這個bug來竊取你的資料庫資料了”
  • 說白了,攔截器就是起一個攔截作用嘛
  • 說到這裡,左前後端互動的同學們都注意了哈,前後臺互動一定要遵循一個原則:互不信任原則。怎麼說呢,就是前端傳送到後臺的引數(必須在前端驗證合法的才能傳送),後臺必須驗證是否合法(是否符合該引數的原定資料型別和值範圍),後臺返回給前端的資料,也必須驗證是否為約定的資料結構和值型別。

進入正題 —- 前端的攔截器原理和實現

  • 上面也說到了,攔截器就是在你的請求要做接下來的處理時,多一次或多次驗證。先看一段示例:前端的攔截器怎麼寫(這裡引用第三方的ajax庫 –> axios,其他ajax庫可參考)
    // http請求攔截
    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響應攔截 axios.interceptors.response.use(response => { switch (response.data.requestIntercept) { case 1: console.log('登入資訊已失效,請重新登入!'); } return response; });
  • 這個示例中設定了前端向服務端發起請求時的http請求攔截和服務端返回資料時的http響應攔截,interceptors是axios的一個攔截器物件,axios.interceptors.request是對http請求攔截配置的物件,這裡我設定了給每個請求新增一個系統當前的時間和一個使用者名稱(實際專案中新增使用者名稱變數),這樣可以避免get請求出現304,並且每次發起請求都向伺服器傳送一次使用者名稱,axios.interceptors.response是對http響應攔截配置的物件,這裡如果伺服器返回json為{requestIntercept: 1},則判定伺服器拒絕了頁面的http請求的執行,直接返回一個狀態提示,否則就返回正常的 response。
  • 前端這樣設定了攔截器就一勞永逸了嗎?當然不是的,前端的永遠不是安全的

安全級別的攔截器 —- nodejs服務端的攔截器原理和實現

  • 服務端的攔截器才是安全的,先看下面這段簡單的攔截器程式碼,主要攔截的是沒有使用者名稱或有使用者名稱但在服務端沒有對應的session的http請求
    // 攔截器
    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 });  // 頁面拿到這個值在做攔截處理即可
            }
        }
    });
  • app.all(‘/*’),這裡的app是 express() 物件,app.all() 是針對所有的http請求, ‘/*’匹配的是所有以“/”開頭的http請求,後面執行的實際上相當於是一個介面,三個引數分別是request,response,next,其中next是攔截器通過的回撥函式
  • 這裡的思路就是先判斷請求是否為登入介面,不是的話就取出請求中的user引數,用這個user去驗證兩邊的cookie是否相同,若不同則直接返回{ requestIntercept: 1 }這個json,告訴前端驗證不通過;驗證通過的呼叫next()回撥函式進入下一個處理環節 — 資料讀取介面

完整的前後端互動攔截器示例:

  • 完整的攔截器設定:(以 /getlist 介面為例)

    1. 前端發起/getlist 介面的http請求,攔截並驗證請求
    2. 服務端通過app.all(‘/*’),匹配到/getlist 介面是 /* 開頭的請求,立即攔截/getlist 介面的http請求並驗證
    3. 驗證通過後,呼叫next()方法將後續處理交給 app.('/getlist') 處理
    4. app.('/getlist') 處理完成後返回資料給前端
    5. 前端驗證返回的json資料是不是{requestIntercept: 1},若不是則交給 axios.post('/getList').then() 處理,至此一次http請求完成,若返回的json資料是{requestIntercept: 1},那麼在 axios.interceptors.response 就會被攔截,同時會告知axios.post('/getList') 物件將promise物件的狀態由 pending(promise正在非同步執行中) 改為 resolved(promise執行完畢)
  • 前端: index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>login</title>
        <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>
    </head>

    <body>
        <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="獲取資料" @click="getList">
            <input type="button" value="登出" @click="logout">
        </div>
        <script type='text/javascript' src='./js/vue.min.js'></script>
        <script src="https://cdn.bootcss.com/axios/0.16.2/axios.min.js"></script>
        <script type='text/javascript'>
            // http請求攔截
            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響應攔截
            axios.interceptors.response.use(response => {
                switch (response.data.requestIntercept) {
                    case 1:
                        console.log('登入資訊已失效,請重新登入!');
                }
                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('出錯了-,-!', err);
                        })
                    },
                    getList() {
                        axios.post('/getList', {
                            // "user": "admin"
                        }).then((res) => {
                            console.log(res.data);
                        }).catch((err) => {
                            console.log('出錯了-,-!', err);
                        })
                    },
                    logout() {
                        axios.post('/logout', {
                            // "user": "admin"
                        }).then((res) => {
                            console.log(res.data);
                            if (res.data.logout == 1) {
                                alert('登出成功');
                            }
                        }).catch((err) => {
                            console.log('出錯了-,-!', err);
                        })
                    }
                }
            })
        </script>
    </body>
    </html>
  • 服務端: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')));  // 預設首頁為www下的index.html
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extend: true }));

    const sessionPool = {};

    const pool = mysql.createPool({
        host: 'mysql資料庫IP',
        user: 'mysql連線使用者,最高許可權使用者為root',
        password: '填寫你的密碼',
        port: '資料庫埠,預設3306',
        database: '使用的mysql資料庫名',
        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 });  // 頁面拿到這個值在做攔截處理即可
            }
        }
    });

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

    // 測試介面
    app.get('/', function (req, res) {
        res.json({ test: `測試伺服器正常!` });
    })

    // 登入介面
    app.post('/login', function (req, res) {
        // 判斷是否已線上
        if (sessionPool[req.body.user]) {
            // 線上
            delete sessionPool[req.body.user];
        }
        // 使用資料庫連線池
        pool.getConnection(function (err, connection) {
            // 多語句查詢示例
            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) {
                        // 設定cookie
                        let cookieSid = req.body.user + Date.parse(new Date());
                        res.setHeader("Set-Cookie", ["sid=" + cookieSid + ";path=/;expires=" + new Date("2030")]);
                        // 先儲存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('[email protected] 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;
    }

相關推薦

nodejs axios前後http互動攔截原理實現()

nodejs 和 axios 在http互動中的簡易攔截器(一) nodejs –> 基於谷歌V8引擎,使用javascript程式設計實現的一個web服務端程式語言 axios –> 基於ES6新語法promise的一個前端ajax庫

json在前後資料互動的應用

一.什麼是json? json本來是javascript裡的內容,有時後端要傳各種各樣的資料格式來適應前端,所以需要用到json來轉換,用它來表示各種各樣複雜的資料,如物件,陣列,集合,以及集合的集合等資料。 先來了解json是什麼,json是一種輕量級的前

axios前後資料互動

要是想實現資料互動首先安裝axios:        npm install axios --save-dev然後在到vue.js裡引入:    import axios from 'axios';一、跨域配置首先找到config->index.js檔案到裡面進行相關配

vue axios前後資料互動問題(2)

前一篇的介紹中遺留了幾個問題,而且部分內容即success、error那部分有誤,具體內容以本篇的為主。本篇解決的問題如下:問題:1、後臺獲取前端資料的值;         2、前端渲染接收到的後臺的值;         3、在axios中this的指向問題。1、後臺獲取到前

spring web.xml 攔截(Interceptor)的實現原理及程式碼示例

前言:前面2篇部落格,我們分析了Java中過濾器和監聽器的實現原理,今天我們來看看攔截器。1,攔截器的概念    java裡的攔截器是動態攔截Action呼叫的物件,它提供了一種機制可以使開發者在一個Action執行的前後執行一段程式碼,也可以在一個Action執行前阻止其執

前後資料互動axiosjquery ajax的區別

axios作為Vue生態系統中濃墨重彩的一筆,我學習這個東西也是花了一定的時間的。剛開始的時候,也是遇到了很多問題。 逐漸摸透了它的脾氣。 首先說說FormData和Payload兩種資料格式的區別: 先是提交一個FormData的請求試試看: 然後我們看後端: 然後

Python Django 前後資料互動HTTP協議下GET與POST的區別 99%的人都理解錯了HTTPGET與POST的區別(轉自知乎)

99%的人都理解錯了HTTP中GET與POST的區別(轉自知乎)   作者:Larry 連結:https://zhuanlan.zhihu.com/p/22536382 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授

前後資料互動方法原理

       對於想要搞web的新手而言,會用html+css+javascript實現一個頁面沒什麼太大的困難,但是想要前後端實現資料互動,在沒有指導的情況下,可能大多數人都會一頭霧水,往往都會有以下的疑問。 目錄 網站資料處理主要分為三層。 第一層,表

學習網站專案學習 - 基於Django Vue的前後資料互動

目錄 一、前後端互動實現思路 1-1 前端思路 1-2 後端思路 二、前端設計 2-1 預設資料設定 2-1-1 建立檢視元件 DataTest.vue 2-1-2 router.js 路由配置 2-1-3 App.vue配置 2-2 使用axios獲取資料

ssm專案,springMVC前後資料互動方法草圖

引言:之前我學jsp、servlet、ssm相關web專案的時候,都接觸到不同的資料互動方式,多種情況像漿糊一樣腦子裡。乾脆我花了一下午的時間總結歸納了所遇到過的情況,在過程中,我查過資料和以前寫過的程式碼,保證每一種情況都是正確存在的。歸納之後,整個人清爽多了。 讀圖須知:每一種情況做了簡單示

HTTP請求的form datarequest payload的區別(request 後臺無法獲取參數)

origin logger res 部分 padding ble 處理 代碼 恰恰 轉載自:btg.yoyo jQuery的ajax方法和post方法分別發送請求,在後臺Servlet進行處理時結果是不一樣的,比如用$.ajax方法發送請求時(data參數是一個JSON.

HTTP 協議的 Transfer-EncodingContent-Length區別

原文出處 Transfer-Encoding,是一個 HTTP 頭部欄位,字面意思是「傳輸編碼」。實際上,HTTP 協議中還有另外一個頭部與編碼有關:Content-Encoding(內容編碼)。Content-Encoding 通常用於對實體內容進行壓縮編碼,目的是優化傳輸,例如用 gzi

python原生程式碼(無框架,無AJAX技術)前後資料互動

採用form表格的post請求向後端傳遞資料 本系列即儘量採用原生程式碼方式實現瀏覽器與web伺服器之間進行互動;幫助小白們理解web伺服器的工作原理。 採用form表格從前端瀏覽器向後端提交資料是一種最原始的前端傳遞資料的方式,雖然程式碼執行較為麻煩,但是卻有助於深刻理解前後端的互動原

如何在開發時部署執行前後分離的JavaWeb專案

在開發中大型的JavaEE專案時,前後端分離的框架逐漸成為業界的主流,傳統的單機部署前後端在同一個專案中的工程專案越來越少。這類JavaWeb專案的後端通常都採用微服務的架構,後端會被分解為諸多個小專案,然後使用dubbo+zookeeper或者springCloud來構建微服務,前端則會是一個單獨

nodejs 使用axios模組發起http請求,並進行攔截各種請求資料

1、安裝axios npm install axios --save -g 2、先寫好攔截器、服務等 新建一個名為http_server.js的檔案 // http_server.js var axios = require("axios") // 建立axios例項s c

vue+Springboot 前後資料互動(1)

最近使用 vue 的 axios 往後端傳送資料,結果一直報錯,嘗試了多種方法 如果vue專案沒有打包放在 springboot 專案下的話,需要開啟跨域支援 在 vue 專案裡 config 目錄下的 index.js 檔案,找到 proxyTable   加上 '

ssm ajax 前後資料互動 (包括出現的406錯誤)

廢話 我哥從我大一的時候,就告訴我要寫部落格,我感覺好對不起他,現在大三了,才開始寫第一篇。。。 剛把ssm教程看完,然後就想用ajax實現前後端分離。於是乎,我搞了兩天才成功。。。 一、搭建環境 首先是ssm的一堆配置檔案,我很迷。。 都是從視訊裡扒下來的,對裡面的東西還不太瞭

利用Nginx解決前後分離專案的跨域問題

1. 前端專案利用Nginx配置站點 server { listen 8092 default_server; listen [::]:8092 default_server; root /home/chenpeng/xiahuaida/data/vue; index index.ht

Vue2配置axios跨域從後取資料賦值(踩坑記

Vue2配置axios跨域 這個系列主要記錄自己實習期間的踩坑過程,不完全準確,只能說這個方法確實解決了我自己的問題,歡迎交流,但不喜勿噴: main.js import Axios from 'axios' Vue.config.productionTip =

表單+node(實現前後資料互動

('',(req,res) => { // req.query是讀取前臺的表單中的所有資料 var name = req.query.name; // 獲取name var pass = req.query.password // 獲取pass var i = 0