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 20

專題:小型課程討論區

把路由、模板、資料庫、API、登入與權限組成一個網站
Interaction實作 45 minute(s)

Learning Objectives

  • 說明一個有登入、資料庫與 API 的網站,各個檔案與各層分別負責什麼
  • 在現成的網站裡找到缺少的權限檢查與驗證,並把它補上
  • 依照既有的寫法替 API 補上一個新路由
  • 用 pytest 替權限與錯誤路徑寫測試,並說明為什麼值得
  • 規劃接下來要學的工具,以及這個網站還可以怎麼延伸

要做出什麼

前面每一課都只處理一個觀念。這一課把它們組成一個能用的網站:小型課程討論區。你會先操作完整的網站,再把故意拿掉的幾段補回來。

功能 用到的觀念 課次
註冊、登入、登出 雜湊密碼、Session 14、15
文章列表、發文、編輯 路由、模板、表單、PRG 3、6~8
資料存進 SQLite、軟刪除 參數化查詢、commit 9、10
只有作者能改、管理員可刪 資料擁有者檢查、角色 16
回覆走 JSON API,頁面不重新載入 API、fetch、textContent 11~13
拆成幾個檔案、自動測試 應用程式工廠、Blueprint、pytest 18

檔案怎麼分:

檔案 內容
app.py create_app():把設定、資料庫、兩個 Blueprint 組起來
pages.py 回傳 HTML 的路由,給瀏覽器
api.py 回傳 JSON 的路由,給 fetch 和其他程式
common.py 兩邊共用:get_db、current_user、login_required、get_post
schema.sql、seed.sql 三張資料表(users、posts、replies)與示範文章
templates/ Jinja2 模板,都繼承 base.html
static/ forum.js(回覆用 fetch 送出)、style.css

路由一覽:

方法與路徑 做什麼 誰可以
GET /、GET /posts/<id> 看文章 任何人
POST /posts/new 發文 登入者
POST /posts/<id>/edit 編輯 只有作者
POST /posts/<id>/delete 軟刪除 作者或管理員
GET /api/posts 文章列表(JSON、分頁) 任何人
GET /api/posts/<id> 文章與回覆(JSON) 任何人
POST /api/posts/<id>/replies 新增回覆 登入者

逐步看:一則回覆要通過哪些檢查

這是把 add_reply 抽出來的簡化版。同一個路由送四次:還沒登入、文章不存在、內容空白、全部正確。注意每一次在哪一行離開函數,以及狀態碼有什麼不同。

示範帳號與操作方式

下面是完整的討論區,程式就在編輯器裡。有三個示範帳號,密碼都是 pw12345678:

帳號 角色 說明
[email protected] 管理員 陳老師,可以刪除任何文章
[email protected] 學生 文章 2 的作者
[email protected] 學生 文章 1 的作者

送出回覆時,注意下面的「請求紀錄」只多了一行 POST /api/posts/…/replies,頁面沒有重新載入;開始操作前也可以先看一下「資料庫」面板。

回覆的按鈕沒有放在 <form> 裡,是因為這個模擬瀏覽器會接手所有表單。真實的網站也常見另一種寫法:用表單的 submit 事件,先呼叫 event.preventDefault() 再 fetch。

互動練習:操作完整的討論區

按「啟動伺服器」,依任務操作。網址列可以直接輸入路徑並按 Enter。下面的 API 工具和瀏覽器共用同一份 Cookie,所以登入後送的請求也是登入狀態。

換你補上缺的部分

下一個練習的網站看起來一樣,但少了三段程式,都標了 TODO。這三個問題光看畫面不一定發現得了,所以先重現問題,再修:

  1. pages.py 的 edit_post:畫面上沒有編輯連結,但沒有檢查作者(第 16 課的漏洞)
  2. api.py 的 add_reply:沒有驗證內容,空白回覆也會寫進資料庫
  3. api.py:少了 GET /api/posts/<id> 這個路由

每次改完按「存檔並重新啟動」,資料庫和登入狀態都會保留。程式出錯時,錯誤訊息在「伺服器紀錄」。卡住時可以按「還原程式」重來,也可以問右下角的陪讀 AI 助教。

修權限時,只擋別人還不夠,要確認自己的文章還能編輯:任務會檢查這兩件事。驗證也一樣,空白要擋,正常的回覆要能通過。

互動練習:補上權限、驗證與新路由

用 [email protected](密碼 pw12345678)登入後開始。文章 1 是 Ben 的,文章 2 是 Amy 的。

逐步看:讓測試幫你記住這些規則

上面的權限與驗證,你是用手操作確認的。手動測試很容易下次就忘了做,所以把它寫成測試。這裡真的執行 pytest,測的就是上一個練習的完整版程式。按到最後一步看輸出,測試內容在輸出區的 test_forum.py 分頁。想看一個測試怎麼逐行執行,可以回到第 18 課。

這幾個測試在保護什麼

打開輸出區的 test_forum.py,它做了幾件值得照抄的事:

  • client fixture 用 tmp_path:每個測試都有一個全新的資料庫,彼此不影響,也不會弄髒開發用的 site.db
  • login_as:用 session_transaction 直接設定身分。登入流程本身另外測一次就好,不必每個測試都走表單
  • 錯誤路徑比正常路徑多:沒登入、空白內容、別人的文章。安全問題幾乎都在這裡,也是手動操作時最容易略過的地方
  • 同一個測試裡比較兩種身分:test_only_author_can_edit 同時確認「別人不行」和「自己可以」,防止你把程式改成全部擋掉

如果有人日後改了 edit_post、不小心刪掉作者檢查,test_only_author_can_edit 會立刻失敗。這就是測試的價值:規則寫下來之後,由程式負責記住。

輸出裡的 0.00s 是這個環境固定的值,在你的電腦上會是實際的秒數。

動手做:刪除文章的權限

完成 delete_post,檢查順序很重要:

  1. 沒登入(user 是 None):abort(401)
  2. 文章不存在或已刪除(post 是 None):abort(404)
  3. 不是作者,也不是 admin:abort(403)
  4. 通過才軟刪除:is_deleted 設成 1,資料列留著

起始的寫法任何人都能刪,而且真的把資料刪掉。程式最下面的測試不要改。

你的輸出
預期輸出

想一想

1.

文章頁已經用 {% if me.id == post.user_id %} 把編輯連結藏起來了。edit_post 裡還需要檢查作者嗎?

2.

回覆內容是 <img src=x onerror=alert(1)>。前端寫 item.innerHTML = data.body 會發生什麼?

3.

api.py 為什麼要有 @api.errorhandler(404)?

4.

新增回覆時,user_id 為什麼從 session 取,不讓前端放在 JSON 裡傳過來?

5.

測試的 client fixture 為什麼要用 tmp_path 建立資料庫,不直接用 site.db?

第 1 次作答

延伸:接下來可以學什麼

把這個討論區做得更完整:

  • 文章列表加上分頁與搜尋(GET /api/posts 已經有 page,前端還沒用)
  • 回覆也能編輯與刪除:套用同樣的擁有者檢查,並補上測試
  • 加上 CSRF token(第 17 課)。這個專題目前只靠 SameSite=Lax
  • 登入嘗試限流、註冊時驗證信箱
  • 照第 19 課的檢查清單實際部署一次

這個專題刻意自己寫的部分,實務上常用現成的工具:

工具 取代什麼
Flask-Login 自己寫的 login_required、current_user、記住我
Flask-WTF 表單驗證與 CSRF token
Flask-SQLAlchemy 手寫 SQL 與連線管理
Flask-Migrate 手動改資料表結構:schema.sql 只能建表,改欄位要靠遷移工具

先自己寫過一次,你才知道這些工具幫你做了什麼。

其他軌道:

重點整理:

  • 路由分成頁面與 API 兩個 Blueprint,共用的邏輯放一起,每個檔案只做一件事
  • 每個寫入都依序檢查:登入、資料存在、權限、內容,全部由伺服器判斷
  • 身分來自 session,不來自請求內容;SQL 一律參數化;放進頁面的內容用 Jinja2 自動跳脫或 textContent
  • 手動試過的規則寫成測試,尤其是錯誤路徑

你已經走完後端開發軌:從一個 HTTP 請求開始,到一個有登入、資料庫、API、測試,也知道怎麼上線的網站。

這一課完成了!
看我的學習進度
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。