National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 15

註冊與登入

密碼雜湊、登入與登出、login_required 裝飾器
Interaction實作 30 minute(s)

Learning Objectives

  • 說明密碼為什麼要雜湊後才存,以及雜湊和加密的差別
  • 用 generate_password_hash、check_password_hash 寫出註冊與登入
  • 說明登入狀態怎麼放進 session,以及登入成功時為什麼要先清空 session
  • 用裝飾器寫出 login_required,保護需要登入的頁面

登入要解決什麼問題

上一課看到 session 可以記住「這個瀏覽器是誰」。這一課補上前面那一段:怎麼確認對方真的是他自稱的人。

做法分成兩半:註冊時把帳號和密碼存起來,登入時比對。問題在於密碼不能照原樣存。資料庫外洩的事件時常發生,如果存的是明文,攻擊者立刻拿到所有人的密碼,而且很多人會在不同網站用同一組密碼。

所以資料庫裡存的是雜湊值(hash):一個只能從密碼算出來、不能倒回去的字串。

逐步看:雜湊怎麼比對

同一個密碼雜湊兩次,請比較兩次的結果,再看 check_password_hash 怎麼判斷。

雜湊、加密與 salt

雜湊 加密
能不能還原 不能 有金鑰就能
適合 密碼:只需要比對,不需要知道原文 之後要讀回來的資料

存密碼不要用 MD5、SHA-256 這類快速雜湊。它們設計成算得很快,攻擊者每秒可以試非常多次。scrypt、Argon2、bcrypt 這類演算法刻意算得慢、佔記憶體,對正常登入影響不大,對大量猜測的成本高很多。詳細建議見 OWASP Password Storage Cheat Sheet。

generate_password_hash 已經幫你處理 salt 與演算法。不要自己設計密碼的儲存方式。

逐步看:一次登入走過哪些程式

程式用 test_client 依序送出:還沒登入就開帳號頁、註冊、打錯密碼、登入、再開帳號頁。注意呼叫堆疊什麼時候進到 wrapped,又在哪裡被擋下來。

互動練習:操作一個會登入的網站

按「啟動伺服器」,在下面的模擬瀏覽器裡實際操作。每做一步,都可以切到下面的面板看發生了什麼:

  • 資料庫:密碼存成什麼樣子
  • Cookie:登入後瀏覽器多了什麼
  • Session:Cookie 裡的內容其實看得到,但改了之後簽章就不符

為什麼 login_required 要寫成裝飾器

需要登入的頁面可能有幾十個。每個 view 函數開頭都寫一次檢查,總有一次會漏掉,漏掉就是資安問題。裝飾器把這段檢查寫在一個地方:

@app.route('/account')
@login_required          # 順序:路由在最上面,login_required 在下面
def account():
    ...

順序不能反過來。裝飾器由下往上套用,@app.route 登記的是它拿到的函數;如果 @login_required 寫在 @app.route 上面,登記進路由的會是沒有檢查的原函數,頁面就沒有被保護。

另外,登出用 POST,不用 GET:GET 應該只讀資料、不改狀態。如果 /logout 接受 GET,別的網站只要放一個連結,你一點就被登出了。這類問題第 17 課再談。

動手做:保護兩個頁面

完成 login_required,讓還沒登入的人被轉到 /login,登入後才能看到內容。程式最下面的測試不要改。

你的輸出
預期輸出

想一想

1.

資料庫外洩後,為什麼存 scrypt 雜湊值比存 SHA-256 雜湊值好?

2.

登入失敗時,下面哪個訊息比較適當?

3.

下面的寫法有什麼問題?

@login_required
@app.route('/account')
def account(): ...
4.

Flask 的 session 預設存在哪裡?

第 1 次作答

重點整理

  • 密碼只存雜湊值,用 generate_password_hash、check_password_hash,不要自己設計
  • 雜湊不能還原;salt 讓相同的密碼得到不同的結果;存密碼要用刻意變慢的演算法
  • 登入失敗的訊息不要透露是信箱還是密碼錯
  • 登入成功時先 session.clear() 再寫入使用者編號;session 不放機密
  • 用 login_required 裝飾器集中檢查,@app.route 寫在最上面

這一課只解決「你是誰」。下一課處理「你可以做什麼」:登入的人可以看別人的筆記嗎?

這一課完成了!
Next lesson:權限與資料的擁有者
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。