第4章封面圖:Section 04 會員註冊登入與角色授權

這章要學會什麼

  • 用現成的會員系統框架完成註冊、登入、登出,並知道密碼在資料庫裡存的其實是一段雜湊值,不是明碼。
  • 分清楚「認證」(你是誰)跟「授權」(你能做什麼)是兩件不同的事。
  • 讓「管理後臺」整區自動要求管理員角色、「會員中心」整區自動要求登入,之後新增頁面忘了加保護也不會變成公開頁面。
  • 讓給程式呼叫的網址(API)在未登入或權限不足時回傳明確的錯誤代碼,而不是轉址到登入頁面。
  • 做出連續猜錯密碼會被鎖定、所有送出資料的表單都驗證來源、註冊表單不能自己指定角色、以及看不到別人資料的保護。

先備知識

要先完成第 3 章:資料庫已經建好並匯入示範會員(此時這些會員都還沒有密碼)。要知道 HTTP 狀態碼裡 302(轉址)、401(沒登入)、403(登入了但沒權限)、404(找不到)分別代表什麼意思。

觀念白話講

為什麼不要自己寫一套登入機制

自己手寫登入功能最常出現三個大漏洞:密碼直接用弱加密方式或甚至明碼存起來(資料庫一旦外洩就全部曝光)、沒有限制連續猜錯的次數(可以無限次試密碼)、登入狀態存放在容易被惡意腳本讀取的地方。這門課直接用官方維護的會員系統框架,這些問題框架都已經處理好:

項目框架怎麼做這門課的設定
密碼儲存用不可逆的加鹽雜湊方式,存的是一段雜湊值密碼至少 8 個字元,要含大小寫、數字、符號
防止暴力猜密碼累計失敗次數,到上限就鎖定一段時間連續錯 5 次鎖定 15 分鐘
登入狀態加密簽章過的通行憑證,只有伺服器解得開惡意腳本讀不到,8 小時內操作會自動延長效期
角色框架內建角色機制本課程只用到一個「管理員」角色

「認證」跟「授權」是兩件不同的事

一個會員中心請求要經過的關卡:①請求帶著登入憑證→②框架驗證憑證、還原出「這是誰」→③依政策檢查「這個人能不能做這件事」→④控制器從登入資訊取出使用者 Id→⑤服務層再檢查「這筆資料的擁有者是不是他本人」。
圖 4-1 一個會員中心請求要經過的關卡:①請求帶著登入憑證→②框架驗證憑證、還原出「這是誰」→③依政策檢查「這個人能不能做這件事」→④控制器從登入資訊取出使用者 Id→⑤服務層再檢查「這筆資料的擁有者是不是他本人」。

認證(Authentication)回答「你是誰」;授權(Authorization)回答「你能做什麼」。這張圖裡第 ③ 步跟第 ⑤ 步是兩種完全不同的檢查,缺一不可:第 ③ 步只知道「這個人有沒有登入、是不是管理員」,並不知道「這筆資料是不是他的」;所以就算某位會員通過了第 ③ 步的檢查,還是要靠第 ⑤ 步在服務層擋住他去看別人的資料。

角色、政策,跟「整區自動保護」的慣例

「角色」是貼在會員身上的標籤(本課程只有「管理員」這一種);「政策」則是一組有名字的授權條件,例如「必須已登入且角色包含管理員」。最容易出現的漏洞就是忘記在新增的頁面上加保護——這門課的解法是寫一個「慣例」:網站啟動時掃描所有頁面控制器,只要屬於「管理後臺」這個區塊,就自動套上「必須是管理員」的政策。之後不管再新增多少後臺頁面,都自動受到保護,不必每次都手動記得加。

給一般網頁用的保護方式(沒登入轉去登入頁)跟給程式呼叫用的保護方式(沒登入直接回錯誤代碼)也刻意分開處理:程式呼叫拿到一整包 HTML 登入頁面是沒有意義的,明確的錯誤代碼才有用。

登入憑證跟「防偽造請求」的雙重防護

登入成功後,伺服器會發一個特殊設定的通行憑證:惡意腳本讀不到它,而且瀏覽器預設不會把它帶去跨網站的表單請求。但光這樣還不夠保險,所以再加一層「防偽造請求權杖」:每個表單都會自動帶一個隱藏欄位,伺服器收到時要跟另一份存放的值核對是否成對——這門課讓全站所有會修改資料的請求都自動做這個檢查,不必每個頁面各自記得加。連「登出」都刻意設計成一定要用會被檢查的方式送出,否則別人可以在別的網站放一個小圖片,趁你不注意就把你登出。

登入失敗時,要怎麼「什麼都不透露」

登入的判斷順序:先用帳號找會員→找不到跟密碼錯誤回同一句話→帳號被鎖定的話連正確密碼也不接受→密碼正確之後才檢查帳號有沒有被停權→都通過才真的發出登入憑證。
圖 4-2 登入的判斷順序:先用帳號找會員→找不到跟密碼錯誤回同一句話→帳號被鎖定的話連正確密碼也不接受→密碼正確之後才檢查帳號有沒有被停權→都通過才真的發出登入憑證。
設計重點:「帳號不存在」跟「密碼錯誤」要顯示完全相同的錯誤訊息,否則攻擊者可以拿這句話的差異去試探「這個信箱有沒有註冊過」。「帳號已停權」這句話則刻意只在密碼驗證正確之後才顯示,避免不知道密碼的人也能看出這個帳號已經被停權。
本章限制:還沒有做寄信驗證信箱、忘記密碼這類功能;示範帳號的密碼要靠自己另外設定才能登入;已登入的通行憑證在效期內(8 小時)就算之後被管理員停權,也要等下一次瀏覽器重新核對才會失效(下一章詳細處理)。

老師示範做了什麼

示範情境:分別用「訪客」「一般會員」「管理員」三種身分打同一批網址,證明後臺跟管理用的程式呼叫入口只有管理員進得去、一般會員完全看不到別人的資料;接著示範登入失敗與帳號鎖定的畫面。

登入失敗的畫面:紅字寫「帳號或密碼錯誤」,沒有指出到底哪一個錯了;帳號欄位保留剛剛輸入的內容,但密碼欄位會被清空;右上角還是顯示「登入/註冊」,代表確實還沒有登入成功。
圖 4-3 登入失敗的畫面:紅字寫「帳號或密碼錯誤」,沒有指出到底哪一個錯了;帳號欄位保留剛剛輸入的內容,但密碼欄位會被清空;右上角還是顯示「登入/註冊」,代表確實還沒有登入成功。
以一般會員登入後的會員中心:右上角顯示這位會員的名字跟登出按鈕;導覽列<strong>沒有</strong>「管理後臺」這個連結;下方三張卡片全部都是這位會員自己的服務。
圖 4-4 以一般會員登入後的會員中心:右上角顯示這位會員的名字跟登出按鈕;導覽列沒有「管理後臺」這個連結;下方三張卡片全部都是這位會員自己的服務。
一般會員直接在網址列輸入後臺網址,被導向「沒有權限」的頁面:網址列可以看出是因為「權限政策不通過」被擋下(不是因為沒登入),頁面也清楚告訴使用者目前是用哪個身分登入的。
圖 4-5 一般會員直接在網址列輸入後臺網址,被導向「沒有權限」的頁面:網址列可以看出是因為「權限政策不通過」被擋下(不是因為沒登入),頁面也清楚告訴使用者目前是用哪個身分登入的。

老師接著用三種身分打了同一批網址,量出一份完整的權限對照表:訪客碰後臺頁面跟管理用程式呼叫都被要求先登入;一般會員碰後臺頁面被導向「沒有權限」、碰管理用程式呼叫得到明確的權限不足錯誤代碼、碰別人的資料得到「找不到」;管理員則兩邊都暢通無阻。這張表證明了「導覽列自動幫忙」的整區保護慣例,是真的在每一支後臺程式上生效,而不是只是頁面上剛好沒有連結而已。

自己動手的步驟

步驟一:啟用會員系統框架與登入通行憑證

設定密碼規則(至少 8 字元、連錯 5 次鎖 15 分鐘)、把登入通行憑證設成「惡意腳本讀不到」、8 小時滑動效期;並且針對「給程式呼叫用的網址」設定成:沒登入直接回錯誤代碼,而不是轉址到登入頁面。最後定義兩個政策:「必須是管理員」跟「只要登入即可」。

步驟二:整區自動保護,跟全站防偽造請求

寫一個「慣例」類別,網站啟動時對每一個頁面控制器執行一次:只要發現它屬於「管理後臺」這個區塊,就自動套上「必須是管理員」的政策;屬於「會員中心」的話套上「只要登入即可」。另外開啟一個全域設定,讓所有會修改資料的請求都自動檢查防偽造請求權杖,不必每個頁面各自加註記。給程式呼叫用的管理入口不屬於任何區塊,所以要在程式碼上明確標註需要的政策。

步驟三:註冊表單與登入流程

註冊表單只有信箱、顯示名稱、密碼、確認密碼四個欄位——沒有任何欄位可以讓使用者自己指定角色,角色一律由管理員事後指派。登入的判斷順序照圖 4-2:先只檢查密碼是否正確(此時還不會發出登入憑證),密碼正確之後才檢查帳號有沒有被停權,都通過才真正發出憑證並轉回原本要去的頁面(而且只接受站內網址,避免被騙去外部的釣魚網站)。

常見誤區:如果用「一步到位」的登入方法(一次驗證密碼同時直接發憑證),就沒有機會在中間插入「檢查帳號是否停權」這一步,停權的帳號反而還是登得進去。

步驟四:管理員的初始帳號,跟「只看自己的資料」

用一段初始化程式,只在角色資料表裡沒有「管理員」這個角色時才建立它,並且只有指定的一個帳號會被加進這個角色——角色的唯一來源就是這段初始化程式跟後續管理員自己的操作介面,注冊表單完全碰不到這件事。

會員讀自己資料的詳細頁面,查詢條件同時帶「這筆資料的 Id」與「擁有者必須是目前登入的人」兩個條件;查不到的話一律回傳「找不到」(不是「權限不足」)——因為回傳「權限不足」等於告訴攻擊者「這個 Id 確實存在,只是不是你的」,反而洩漏了資訊。

常見錯誤

症狀原因怎麼處理
手寫的登出表單送出得到失敗全站防偽造請求檢查生效,但手寫表單沒有自動帶上隱藏欄位改用框架提供的表單標籤語法
註冊時密碼一直被拒絕密碼不符合最短長度或字元種類要求這是預期中的保護,換一個包含大小寫、數字、符號的密碼
示範帳號登不進去,提示「暫時無法登入」忘記事先設定示範帳號的密碼先設定密碼,再重新啟動網站

怎麼驗收+反向驗證

  1. 一般會員直連後臺會失敗:以一般會員登入後在網址列直接輸入後臺網址,一律被導向「沒有權限」頁面。
  2. 一般會員直連管理用程式呼叫也會失敗:得到明確的權限不足錯誤代碼,且沒有任何資料內容;登出後再打得到「未登入」的錯誤代碼;換管理員登入則能拿到完整資料。
  3. A 會員看不到 B 會員的資料:打開別人的資料詳細頁得到「找不到」,打開自己的正常顯示。
  4. 反向驗證:用工具送出註冊表單時額外夾帶「角色=管理員」「啟用狀態=停用」這兩個表單模型裡根本沒有的欄位,註冊仍然成功,但資料庫裡這個新帳號的角色是空的、啟用狀態仍是預設值——證明多塞的欄位確實被安靜忽略了。
← 上一章 回課程地圖 下一章 →