跨店跨會員

🧱 多租戶系統的核心:Tenant Boundary(租戶邊界)
不論是「店」或「會員」,都必須有一個一致的「租戶識別」,且整個系統要從上到下都尊重這邊界
1 | 使用者 → Token → Context → DbContext / Repository → SQL |
只要其中一層沒守好,就可能造成資料外洩想像你有「很多間分店」都使用同一套後台系統
- A 店的會員不能看到 B 店的訂單
- B 店的商品不能被 A 店誤改
- C 店的營收絕不能被其他店家查到
👉 ShopId 就像房間鑰匙,沒有鑰匙就不能進別人的房間
🧱 後端層面防範策略(最重要的三層)
這三層是「後端防呆」的核心,只要其中一層沒做好,就可能讓使用者查到不是自己的資料
1️⃣ Token / 身分驗證層:登入時就綁 ShopId(第一道鎖)
當會員登入後,你發 JWT 時,「必須」把該會員的 ShopId 寫進 Token
1 | { |
以後每次 API 呼叫,後端都要比對
1 | if (user.ShopId != request.ShopId) |
📌 不允許客戶端在 request 裡手動帶 ShopId,因為只要能自己帶 ShopId,就等於能切換成別家店 → 超危險!
2️⃣ 資料查詢層:Global Query Filter 自動帶出 ShopId(第二道鎖)
使用 EF Core 時,最容易發生的就是開發者忘記寫 where ShopId,為避免人為失誤,可以用 Global Query Filter
1 | protected override void OnModelCreating(ModelBuilder modelBuilder) |
所以
1 | context.Orders.ToList(); |
實際 SQL 會變成
1 | SELECT * FROM Orders WHERE ShopId = 'A001'; |
3️⃣ API 授權層:Claim + Policy(第三道鎖)
在 ASP.NET Core 的 Authorization 層加入 ShopId 驗證
1 | options.AddPolicy("ShopScope", policy => |
即使有人嘗試竄改 URL 或 Body 中的 ShopId,也會被攔下
🎨 前端與 API 層的安全策略(使用者看不到的防護)
| 層級 | 防範重點 |
|---|---|
| API Gateway | 驗證 JWT、強制綁定 ShopId,不讓亂帶 |
| Header | ShopId 不能 客戶端提供,由後端自動注入 |
| URL | 避免直接寫 /shop/A001/order,不給人亂猜 |
| 前端 UI | 不顯示不屬於自己的資料,只提供授權內容 |
前端只能「顯示」資料,不負責安全。安全永遠在後端強制
🧍♂️ 會員隔離(Member Isolation)
租戶隔離是「店 vs 店」的隔離,而會員隔離則是同一個店裡的每個會員只能看到「自己的資料」
例如:訂單、點數、收藏列表 Order.MemberId == currentMemberId
📎 Coding Guideline(實作習慣)
✔ Service / Repository 不接受「外部傳入的 ShopId」
只能從 Token / Context 拿
✔ 每個 Entity 必須有 ShopId
1 | public class Order |
✔ API 參數禁止明碼傳 ShopId
GET /shop/A001/orders => GET /orders 後端自動用 Token 取 ShopId。
✔ DbContext 必須存當前 ShopId(Tenant Context)
方便 Global Filter 使用
AB店
https://wiki.91app.com/pages/viewpage.action?pageId=169902529
coding guideline
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.


