今天的重點是理解 身分驗證(Authentication) 與 存取控制(Authorization) 的差別,並用 Python + JWT(JSON Web Token)示範如何設計不同角色的權限,例如「醫師」與「病人」。
一、理論重點
Authentication(身分驗證)
這是確認「你是誰」的過程。常見方式有帳號密碼、OTP 驗證碼、智慧卡,以及生物特徵辨識如指紋或人臉。舉例來說,病人輸入健保卡號與密碼登入醫院系統,系統驗證正確後才允許進入。
Authorization(授權 / 存取控制)
這是確認「你能做什麼」的規則。就算通過登入,也必須依角色決定能存取的資料。例如,病人只能看到自己的檢驗報告,而醫師可以查閱多位病人的完整病歷。
RBAC(角色基礎存取控制)
RBAC 是醫療系統最常見的授權方式。它不是針對個人設定權限,而是依角色分配。像是病人角色僅能查看自己的病歷,醫師角色能查詢所屬病人,管理員則擁有最高權限。這樣設計的好處是管理方便,新使用者只要被指派角色,就會自動繼承對應權限。
JWT(JSON Web Token)
JWT 是常見的驗證機制。當使用者登入後,系統會發給一個 Token 代表其身分。之後每次存取 API,都必須帶上這個 Token,系統才會驗證角色與權限。Token 由三部分組成:Header(演算法)、Payload(使用者資訊與角色)、Signature(數位簽章)。
二、實際案例
病患誤看他人病歷(2018,台灣)
2018 年,台灣某大型醫院的線上掛號與病歷查詢系統曾發生過資安漏洞。部分病人在登入系統後,原本應該只能看到自己的病歷,但因為程式在驗證權限時出錯,結果導致他們能意外查詢到其他病患的個人資訊與醫療紀錄。這不僅違反了個資保護相關法規,也讓病人對醫院的資訊安全產生高度質疑。事件曝光後,醫院緊急修補系統,並承諾加強 角色存取控制 與 身份驗證機制,避免同樣狀況再發生。
三、簡單程式示範
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
import jwt
from datetime import datetime, timedelta
# 建立 FastAPI app
app = FastAPI()
# 假設的秘密金鑰(正式應用要放環境變數)
SECRET_KEY = "mysecretkey"
ALGORITHM = "HS256"
# 模擬的使用者資料庫
fake_users = {
"alice": {"password": "1234", "role": "patient"},
"bob": {"password": "5678", "role": "doctor"},
"admin": {"password": "9999", "role": "admin"}
}
# 定義登入用的請求資料格式
class LoginRequest(BaseModel):
username: str
password: str
# 產生 JWT Token
def create_token(username: str, role: str):
expire = datetime.utcnow() + timedelta(minutes=30)
payload = {"sub": username, "role": role, "exp": expire}
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
# 驗證 Token
def decode_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# 登入 API(不需要 client_id / client_secret)
@app.post("/login")
def login(req: LoginRequest):
user = fake_users.get(req.username)
if not user or user["password"] != req.password:
raise HTTPException(status_code=401, detail="帳號或密碼錯誤")
token = create_token(req.username, user["role"])
return {"access_token": token}
# 病人專屬 API
@app.get("/patient")
def get_patient_data(token: str):
payload = decode_token(token)
if payload["role"] != "patient":
raise HTTPException(status_code=403, detail="沒有病人權限")
return {"message": f"Hello {payload['sub']}, 這是你的檢驗報告"}
# 醫師專屬 API
@app.get("/doctor")
def get_doctor_data(token: str):
payload = decode_token(token)
if payload["role"] != "doctor":
raise HTTPException(status_code=403, detail="沒有醫師權限")
return {"message": f"Hello {payload['sub']}, 你可以存取病人的完整病歷"}
# 管理員專屬 API
@app.get("/admin")
def get_admin_data(token: str):
payload = decode_token(token)
if payload["role"] != "admin":
raise HTTPException(status_code=403, detail="沒有管理員權限")
return {"message": f"Hello {payload['sub']}, 你擁有最高權限"}
這個範例示範了「使用者登入獲得 JWT → 每次 API 呼叫以 token 驗證 → 根據 token 內的 role 決定是否允許存取」,以簡單明確的方式模擬醫療系統中醫師與病人的 RBAC 權限管理流程。執行結果如下