[ 筆記 ] Express 02 - Middleware, hash, escape


Posted by krebikshaw on 2020-10-03

初探 Middleware

在 Express 這個框架裡面 Middleware 扮演著中繼站的角色,當 Server 收到 request 之後,到回傳 response 的這段時間,會經過一系列的 Middleware,比如說一個登入的動作可能就包含負責不同功能(輸入帳號,身份驗證,傳輸資料,顯示畫面)的 Middleware 在其中。

每個 Middleware 會有自己負責的功用,比如說我們在上一篇文章提到的 Controller 就是一種 Middleware。

通常 Middleware 都會有一個標準的形式:

(req, res, next) => {
   ...
}

傳入的參數:

  • req:代表 request
  • res:代表 response
  • next:用來表示是否要將 req 跟 res 的控制權傳給下一個 Middleware

我們以一個權限管理的功能舉例:

  • 我們希望使用者一點開網站就做驗證
  • 驗證通過,才會把 req 跟 res 的控制權傳給下一個 Middleware
function checkPermission(req, res, next) {
  if (<驗證條件>) {         // 如果驗證條件通過
    next();                // 才會把 req 跟 res 的控制權傳給下一個 Middleware
  } else {
    res.end('Error');      // 否則就顯示 Error
  }
}

app.use( checkPermission )  // 把 function 傳入 .use()
  • .use() 這個函式,代表這個 Middleware 影響的範圍會是整個 app
  • 如果希望影響的只有其中一個路由,可以把 checkPermission 加在那個路由底下
這樣寫 checkPermission 的這個 Middleware 影響的範圍就只有 './todos' 這個路由而已
app.get('./todos', checkPermission, todoController.getAll)
app.get('./todos/:id', todoController.get)
  • 所以 ./todos 這個路由被觸發的時候,會先執行 checkPermission 這個 Middleware,等到結束後它會把全控制權傳給下一個 todoController.getAll Middleware

常用 Middleware

☞ body-parser

功用:express 本身只能在 URL 上面拿資料(用 req.query 語法),但是 POST 方法會把資料放在 request.body 裡面,所以要利用 body-parser 這個 Middleware 來幫助我們取得夾帶在 body 裡面的資料

安裝:

npm install body-parser

使用方法:

const bodyParser = require('body-parser')   // 引入 body-parser

// 設定要 Parse 的資料格式是 urlencodes 還是 json,通常兩個都會用到,所以都引入進來
app.use(bodyParser.urlencoded({ extended: false }))  // 一般在瀏覽器 POST 的資料格式
app.use(bodyParser.json())  // json 格式的資料

加上上面兩個設定之後,就可以實際用 req.body.<name> 去取得 body 裡面的資料了

瀏覽器端:

<form method="post" action="todos">
  <input type="text" name="content" />
  <input type="submit" />
</form>

伺服器端:

app.post('./todos', (req, res, next) => {
  const content = req.body.content    // 利用 req.body.<name> 去取得 body 裡面的資
})

☞ Express-Session

功用:處理 Session 相關的功能

安裝:

npm install express-session

使用方法:

const session = require('express-session')

app.set('trust proxy', 1)
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  // cookie: { secure: true }
}))

加入上面這些設定之後,就可以實際用 req.session.<參數> 來存取 session 了

app.post('./login', (req, res, next) => {
  if (username === '...' && password === '...' ) {
    req.session.username = username;     //  設定 session.username
    res.redirect('back')                 //  導回上個頁面
  } else {
    res.redirect('/login')
  }
})

在 render 的時候就可以判斷登入狀態決定要顯示的畫面內容

<% if (req.session.username) { %>   //  如果 session.username 有值的話
  <h1>你已經登入</h1>
<% } else { %>
  <h1>你還沒登入</h1>
<% } %>

☞ connect-flash

功用: 可以存放你想使用的訊息,例如:輸入密碼錯誤、帳號已被註冊,之類的提示訊息。先將這些訊息存放在 flash 當中,等到需要顯示這些提示訊息的時候,就把 flash 裡的顯示顯示於畫面上,一但脫離這個畫面,或者重新整理頁面,訊息就會自動消失。

安裝:

npm install connect-flash

使用方法:

const flash = require('connect-flash');
app.use(flash());

// 只要有設定 Express-Session 就不用做 connect-flash 的額外設定

加入上面這些設定之後,就可以實際用 req.flash(<參數>) 來存取 flash 了

  • 使用方式跟 session 很類似
  • flash() 如果帶入兩個參數:代表將數值存入 flash
    • req.flash('errorMassage', 'Please input the correct password')
    • 將 Please input...... 存在 errorMassage 底下
  • flash() 如果只帶一個參數:代表取出存在這個參數底下的 flash
    • req.flash('errorMassage')
    • 取出存在 errorMassage 這個參數底下的 Please input...... 訊息
app.post('./login', (req, res, next) => {
  if (username === '...' && password === '...' ) {
    req.session.username = username;
    res.redirect('back')
  } else {
    req.flash('errorMessage', 'Please input the correct password');  //  將 Please input...... 存在 errorMassage 底下
    res.redirect('/login')
  }
})
app.get('./login', (req, res, next) => {
  res.render('login', {
    errorMessage: req.flash('errorMessage');  //  取出存在 errorMassage 這個參數底下的 Please input...... 訊息
  })
})

如此一來,就可以在 login 失敗的時後,加上顯示給使用者錯誤訊息

  • 如果同一條錯誤訊息,很多地方需要用到,每一次 render 都把參數帶進去其實很麻煩
  • 在 express 有一個叫做 res.locals 的地方,可以讓我們放我們想放入的訊息,並且這些訊息,可以在 view 裡面直接存取到
app.use((req, res, next) => {
  res.locals.errorMessage = req.flash('errorMessage');  //  寫在 res.locals 底下的東西 view 裡面都能拿到
  next()    //  記得要 next 讓下一個 Middleware 可以拿到使用權
})
<% if (req.session.username) { %>   //  如果登入成功
  <h1>你已經登入</h1>
<% } else { %>
  <%= errorMessage %>    //  在 view 裡面直接存取放在 res.locals 底下的訊息 
<% } %>

所以 res.locals 就有點像一個 global 的地方,不同 view 都需要用到的訊息,統一放在這裡,所有的 view 就都能存取的到

Express 的 hash 方法

我們要引入一個 Library 叫做 bcrypt

安裝:

npm install bcrypt

使用方法:

const bcrypt = require('bcrypt');
const saltRounds = 10;   //  設置 hash 的複雜度

bcrypt.hash(password, saltRounds, function(err, hash) {
    // 程式碼: 決定 hash 過後的 password 要如何處理
});

☞ 儲存註冊的密碼:

bcrypt.hash() 需要傳入三個參數:

  • 第一個參數:要做 hash 的變數 (也就是我們的 password)
  • 第二個參數:saltRounds
  • 第三個參數:call back function (決定 hash 過後的 password 要如何處理)
    • 這個 call back function 裡面的第二個參數 hash 代表已經被 hash 過後的密碼
bcrypt.hash(password, saltRounds, function(err, hash) {
  if (err) {
    req.flash('errorMessage', err.toString());
    return res.redirect('back');
  }
  userModel.add({       // 將註冊的帳號密碼傳給 Model
    nickname,
    username,
    password: hash      // 寫進去的密碼就會是 hash 過後的密碼,而不是原來的 password 明碼 
  })
})

☞ 匹配登入的密碼:

bcrypt.compare() :需要傳入三個參數:

  • 第一個參數:使用者輸入的明碼
  • 第二個參數:資料庫存放的 hash 過後的密碼
  • 第三個參數:call back function ( 把使用者輸入的密碼 hash 過後跟資料庫的密碼比對)
    • 這個 call back function 裡面的第二個參數 result,當匹配結果成功的時候就會是 true
bcrypt.compare(password, hash, function(err, result) {
    // 如果 result 為 true 代表匹配結果為正確
    if (err || !res) {
      req.flash('errorMessage', err.toString());
      return res.redirect('back');
    }
    req.session.username = username
    res.redirect('/')
});

Express 的 escape

Express 的 View 已經幫我們做好 escape 了
所以 <%= username %> 如果裡面有 HTML 的程式碼也沒有關係,所以就不用擔心 XSS 的問題。

除非使用 <%- username %> 當我們把符號換成 - 號, tamplate engine 就不會自動幫我們做 escape 了


#Middleware







Related Posts

W9_[ BE101 ] PHP 與 MySQL 實作練習 + w9  直播檢討

W9_[ BE101 ] PHP 與 MySQL 實作練習 + w9 直播檢討

建立屬於你的 Google Map 地圖標記(一) - Google Map API 與 React

建立屬於你的 Google Map 地圖標記(一) - Google Map API 與 React

記一次幫開源專案 spectrum 修 bug 的經歷

記一次幫開源專案 spectrum 修 bug 的經歷


Comments