
這章要學會什麼
- 用現成的會員系統框架完成註冊、登入、登出,並知道密碼在資料庫裡存的其實是一段雜湊值,不是明碼。
- 分清楚「認證」(你是誰)跟「授權」(你能做什麼)是兩件不同的事。
- 讓「管理後臺」整區自動要求管理員角色、「會員中心」整區自動要求登入,之後新增頁面忘了加保護也不會變成公開頁面。
- 讓給程式呼叫的網址(API)在未登入或權限不足時回傳明確的錯誤代碼,而不是轉址到登入頁面。
- 做出連續猜錯密碼會被鎖定、所有送出資料的表單都驗證來源、註冊表單不能自己指定角色、以及看不到別人資料的保護。
先備知識
要先完成第 3 章:資料庫已經建好並匯入示範會員(此時這些會員都還沒有密碼)。要知道 HTTP 狀態碼裡 302(轉址)、401(沒登入)、403(登入了但沒權限)、404(找不到)分別代表什麼意思。
觀念白話講
為什麼不要自己寫一套登入機制
自己手寫登入功能最常出現三個大漏洞:密碼直接用弱加密方式或甚至明碼存起來(資料庫一旦外洩就全部曝光)、沒有限制連續猜錯的次數(可以無限次試密碼)、登入狀態存放在容易被惡意腳本讀取的地方。這門課直接用官方維護的會員系統框架,這些問題框架都已經處理好:
| 項目 | 框架怎麼做 | 這門課的設定 |
|---|---|---|
| 密碼儲存 | 用不可逆的加鹽雜湊方式,存的是一段雜湊值 | 密碼至少 8 個字元,要含大小寫、數字、符號 |
| 防止暴力猜密碼 | 累計失敗次數,到上限就鎖定一段時間 | 連續錯 5 次鎖定 15 分鐘 |
| 登入狀態 | 加密簽章過的通行憑證,只有伺服器解得開 | 惡意腳本讀不到,8 小時內操作會自動延長效期 |
| 角色 | 框架內建角色機制 | 本課程只用到一個「管理員」角色 |
「認證」跟「授權」是兩件不同的事
認證(Authentication)回答「你是誰」;授權(Authorization)回答「你能做什麼」。這張圖裡第 ③ 步跟第 ⑤ 步是兩種完全不同的檢查,缺一不可:第 ③ 步只知道「這個人有沒有登入、是不是管理員」,並不知道「這筆資料是不是他的」;所以就算某位會員通過了第 ③ 步的檢查,還是要靠第 ⑤ 步在服務層擋住他去看別人的資料。
角色、政策,跟「整區自動保護」的慣例
「角色」是貼在會員身上的標籤(本課程只有「管理員」這一種);「政策」則是一組有名字的授權條件,例如「必須已登入且角色包含管理員」。最容易出現的漏洞就是忘記在新增的頁面上加保護——這門課的解法是寫一個「慣例」:網站啟動時掃描所有頁面控制器,只要屬於「管理後臺」這個區塊,就自動套上「必須是管理員」的政策。之後不管再新增多少後臺頁面,都自動受到保護,不必每次都手動記得加。
給一般網頁用的保護方式(沒登入轉去登入頁)跟給程式呼叫用的保護方式(沒登入直接回錯誤代碼)也刻意分開處理:程式呼叫拿到一整包 HTML 登入頁面是沒有意義的,明確的錯誤代碼才有用。
登入憑證跟「防偽造請求」的雙重防護
登入成功後,伺服器會發一個特殊設定的通行憑證:惡意腳本讀不到它,而且瀏覽器預設不會把它帶去跨網站的表單請求。但光這樣還不夠保險,所以再加一層「防偽造請求權杖」:每個表單都會自動帶一個隱藏欄位,伺服器收到時要跟另一份存放的值核對是否成對——這門課讓全站所有會修改資料的請求都自動做這個檢查,不必每個頁面各自記得加。連「登出」都刻意設計成一定要用會被檢查的方式送出,否則別人可以在別的網站放一個小圖片,趁你不注意就把你登出。
登入失敗時,要怎麼「什麼都不透露」
老師示範做了什麼
示範情境:分別用「訪客」「一般會員」「管理員」三種身分打同一批網址,證明後臺跟管理用的程式呼叫入口只有管理員進得去、一般會員完全看不到別人的資料;接著示範登入失敗與帳號鎖定的畫面。
老師接著用三種身分打了同一批網址,量出一份完整的權限對照表:訪客碰後臺頁面跟管理用程式呼叫都被要求先登入;一般會員碰後臺頁面被導向「沒有權限」、碰管理用程式呼叫得到明確的權限不足錯誤代碼、碰別人的資料得到「找不到」;管理員則兩邊都暢通無阻。這張表證明了「導覽列自動幫忙」的整區保護慣例,是真的在每一支後臺程式上生效,而不是只是頁面上剛好沒有連結而已。
自己動手的步驟
步驟一:啟用會員系統框架與登入通行憑證
設定密碼規則(至少 8 字元、連錯 5 次鎖 15 分鐘)、把登入通行憑證設成「惡意腳本讀不到」、8 小時滑動效期;並且針對「給程式呼叫用的網址」設定成:沒登入直接回錯誤代碼,而不是轉址到登入頁面。最後定義兩個政策:「必須是管理員」跟「只要登入即可」。
步驟二:整區自動保護,跟全站防偽造請求
寫一個「慣例」類別,網站啟動時對每一個頁面控制器執行一次:只要發現它屬於「管理後臺」這個區塊,就自動套上「必須是管理員」的政策;屬於「會員中心」的話套上「只要登入即可」。另外開啟一個全域設定,讓所有會修改資料的請求都自動檢查防偽造請求權杖,不必每個頁面各自加註記。給程式呼叫用的管理入口不屬於任何區塊,所以要在程式碼上明確標註需要的政策。
步驟三:註冊表單與登入流程
註冊表單只有信箱、顯示名稱、密碼、確認密碼四個欄位——沒有任何欄位可以讓使用者自己指定角色,角色一律由管理員事後指派。登入的判斷順序照圖 4-2:先只檢查密碼是否正確(此時還不會發出登入憑證),密碼正確之後才檢查帳號有沒有被停權,都通過才真正發出憑證並轉回原本要去的頁面(而且只接受站內網址,避免被騙去外部的釣魚網站)。
步驟四:管理員的初始帳號,跟「只看自己的資料」
用一段初始化程式,只在角色資料表裡沒有「管理員」這個角色時才建立它,並且只有指定的一個帳號會被加進這個角色——角色的唯一來源就是這段初始化程式跟後續管理員自己的操作介面,注冊表單完全碰不到這件事。
會員讀自己資料的詳細頁面,查詢條件同時帶「這筆資料的 Id」與「擁有者必須是目前登入的人」兩個條件;查不到的話一律回傳「找不到」(不是「權限不足」)——因為回傳「權限不足」等於告訴攻擊者「這個 Id 確實存在,只是不是你的」,反而洩漏了資訊。
常見錯誤
| 症狀 | 原因 | 怎麼處理 |
|---|---|---|
| 手寫的登出表單送出得到失敗 | 全站防偽造請求檢查生效,但手寫表單沒有自動帶上隱藏欄位 | 改用框架提供的表單標籤語法 |
| 註冊時密碼一直被拒絕 | 密碼不符合最短長度或字元種類要求 | 這是預期中的保護,換一個包含大小寫、數字、符號的密碼 |
| 示範帳號登不進去,提示「暫時無法登入」 | 忘記事先設定示範帳號的密碼 | 先設定密碼,再重新啟動網站 |
怎麼驗收+反向驗證
- 一般會員直連後臺會失敗:以一般會員登入後在網址列直接輸入後臺網址,一律被導向「沒有權限」頁面。
- 一般會員直連管理用程式呼叫也會失敗:得到明確的權限不足錯誤代碼,且沒有任何資料內容;登出後再打得到「未登入」的錯誤代碼;換管理員登入則能拿到完整資料。
- A 會員看不到 B 會員的資料:打開別人的資料詳細頁得到「找不到」,打開自己的正常顯示。
- 反向驗證:用工具送出註冊表單時額外夾帶「角色=管理員」「啟用狀態=停用」這兩個表單模型裡根本沒有的欄位,註冊仍然成功,但資料庫裡這個新帳號的角色是空的、啟用狀態仍是預設值——證明多塞的欄位確實被安靜忽略了。