要打造安全的 API Server,第一步就是不要讓奇怪的資料跑進來,也就是要做 input validation
只要可以在一開始把奇怪的請求、資料給擋掉,那就可以大幅降低這些資料進到資料庫、進而弄壞某個功能的機會
所以具體而言要做什麼呢?首先是要過濾掉語法或格式錯誤的請求,譬如說信箱格式錯誤、電話號碼長度不對等等,或是你明明要求輸入 4 到 16 字的密碼,使用者卻只給你一大串 30 個字,這些都可以在一開始就過濾掉
雖然這些過濾聽起來很簡單,但實際上還滿瑣碎的,所以我推薦直接用別人的 library,像是 Node.js 的 validator 跟 Go 的 govalidator 都很不錯,這樣不只可以省下許多時間,還可以避免掉自己造輪子的 bug
實際的案例就像這樣,使用者填了他的個人資料送出後,我用 Node.js 的 express-validator(包裝成 middleware 形式的 validator)對信箱、電話做驗證,如果格式有什麼錯誤那就把錯誤送回前端,沒有的話則是請求成功
const { body, validationResult } = require('express-validator')
// vvvv |信箱格式驗證| vvvv vvvvvvv |手機格式驗證| vvvvvvv
app.post('/user', body('email').isEmail(), body('phone').isMobilePhone(), (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() }) // 格式有錯就回傳 400
}
User.create({ username: req.body.username, phone: req.body.phone }) // 格式正確才進資料庫
},
)
除了語法、格式上的錯誤之外,另外一個很容易被忽略的是語義錯誤,譬如說使用者填的出生年份是 2050 年(從未來穿越回來的?)、入住日期比退房日期來得晚、網路訂餐一次訂 5000 份等等
像這類格式正確、內容錯誤的請求最好在一開始就擋掉,畢竟任何資料的值都有一個合理的範圍,如果不小心把這些值存進資料庫,哪天在計算時就可能引發意想不到的錯誤
同樣以 Node.js 為例,如果我做的是一個訂房網站,那除了確定入住、退房日期都是正確的日期格式外,還要確認退房時間比入住時間來得晚才行
// 自己寫一個 middleware 用來確認入住、退房時間
const isCheckInBeforeCheckout = body('checkIn').custom((checkIn, { req }) => {
if(new Date(req.body.checkOut) < new Date(checkIn)) {
// 如果退房時間比入住時間早
throw new Error ('你的入住時間怪怪 der')
}
return true
})
// vvv 確認入住、退房時間 vvv
app.post('/order', /* 其他驗證 */, isCheckInBeforeCheckout, (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
// 沒問題才新增訂單到資料庫
Order.create({ checkIn: req.body.checkIn, checkOut: req.body.checkOut })
},
)
看到這邊你可能會想說:我上 Airbnb 訂房時他的 UI 設計得很好,所以根本不可能發生「入住日期比退房日期來得晚」的情況
但萬一攻擊者是直接用 Postman 或 Curl 這類工具發起惡意請求呢?所以不管你的前端設計、防範得再好,後端的 API Server 收到請求時務必要再做一次完整的檢查,否則這類奇怪的資料一旦進了資料庫,後果將不堪設想
今天用兩個小例子講了 API server 的入口管制應該做什麼事:簡單來說就是不管請求是從哪來的,只要資料進到 API server 就要從嚴處理,而且不管是在格式還是語義上都要進行確人,才不會有一些怪怪的資料趁機滲透進來哦~