在 Keycloak 和 HAPI FHIR 中間放一層 Rails(一):它不是 proxy,是 policy gateway
這系列整理自我跟 AI 一次討論 FHIR 存取架構的對話,把患者端 SMART on FHIR、火線超人(FHIRLineBot)跟 AI 臨床工具要怎麼安全接資料,一次想清楚。
我在規劃患者端的 SMART on FHIR 應用時,卡在同一個問題:細粒度的存取控制到底該放哪裡。病人只能看自己的資料、只能看最近七天、某些欄位要遮蔽、AI Agent 不能亂查別人的病歷,這些規則要塞在哪?
我試過兩個方向。一個是把規則全部硬塞進 HAPI FHIR,用它的 interceptor 去擋。另一個是在 Keycloak 跟 HAPI FHIR 中間,放一層自己的 Rails。試下來我選後者,因為前者很快就會變成一堆散在 Java interceptor 裡、難維護又難稽核的條件判斷。
這一層我定位成 policy gateway,不是 proxy
重點是心態。我不是把 Rails 當成一個「把請求原封不動轉給 HAPI」的透明代理,那樣它只是多一個 hop。我把它定位成 FHIR Policy Gateway,或者說 Clinical Function Gateway,一層真正決定「這次請求在這個情境下,實際能做什麼」的執行層。
整個架構長這樣:
flowchart TB
A[SMART App / LINE Bot / AI Agent] -->|OAuth Access Token| B
B[Rails FHIR Policy Gateway] --> C[HAPI FHIR]
C --> D[Clinical Data Store]
subgraph B1[Rails 這層做的事]
direction TB
T1[驗證 token、scope、patient context]
T2[套用 constraint]
T3[執行 consent / purpose policy]
T4[呼叫 CQL Engine]
T5[提供受控 function tools]
T6[稽核與遮罩]
end
B --- B1
四個角色,一句話各自定位
我把責任拆成四塊,各講一句就懂:
- Keycloak:你是誰、拿到什麼授權。負責登入、OAuth 2.0 / OIDC、SMART scope、patient launch context、token 效期。
- Rails:這次請求在此情境下實際能做什麼。驗 token、正規化 scope、綁 patient、展開 constraint、改寫查詢、過濾回傳、稽核。
- CQL:臨床條件與決策邏輯。判斷病人符不符合某個臨床條件,不拿來當授權。
- HAPI FHIR:標準化的臨床資料來源。標準 FHIR REST、驗證、搜尋、版本、history。
為什麼不把這些全塞 HAPI?因為彈性。像「僅限本人、僅限最近七天、禁止 $everything、遮蔽精神科標籤」這種會一直變的業務政策,放在 Rails 我改起來快,也留得下稽核軌跡。HAPI 我留給它最擅長的事:當標準資料來源,順便當最後一道防線。
這一篇先把定位講清楚。後面幾篇我會拆開講 constraint 怎麼展開、CQL 為什麼不能當授權語言、AI Agent 的 function tooling 怎麼收斂,還有 OPA 跟 Kafka 補進來之後,整張圖怎麼合起來。