淺談微服務架構中的鑒權體系
在微服務架構中,有一個核心的問題是處理好“集權”(中心化)和“放權”(去中心化)的關系。雖然微服務的主旋律是把數(shù)據(jù)和業(yè)務拆成小而獨立的模塊,但我們仍然需要一個強力的中央安保體系來確保“數(shù)據(jù)分散,權限集中”。這一篇就談談微服務架構中的鑒權體系。
身份認證
身份認證(Authentication)的目的是證明“你是你(所號稱的那個人)”。
要證明這一點,你必須掌握一個只有你自己和認證機構才知道的機密信息。在現(xiàn)實中,這個信息可能是 DNA、指紋、虹膜這樣的生物識別特征,但由于這種特征跟人身直接綁定且又不可修改,一旦泄露,可能被持續(xù)冒用,造成不可挽回的嚴重后果,所以現(xiàn)實中較少采用這些生物識別特征作為識別之用。
如果不采用機密信息作為判斷標準,就需要一個持續(xù)的、不易偽造的“證明材料”。在中國,這個證明材料就是戶口或身份證。中國對公民信息的登記相對嚴格,所以會在小孩出生的時候要求把身份信息登記到戶口之中,形成身份證明,跟隨一生。在需要證明“你是你”的時候,拿出身份證就行了。
(生物特征不可廢棄,所以我們必須把它包一層,形成證明材料和對應的 Persona)
與生物識別特征不同的是,身份證如果丟失,從理論上說,應該可以掛失并且讓其失效,然后辦理一張新的身份證。不過,設計我國身份證的機構和供應商也許沒有考慮到這個問題,或者考慮到現(xiàn)實情況太復雜,導致身份證無法掛失,丟失的身份證仍然具備證明效力,但這個是后話了。
為了避免身份證被冒用,在對身份認證要求比較嚴格的場合(比如銀行),會附加一些別的檢查,比如對比照片等等。
那么,現(xiàn)在我們來對身份認證進行規(guī)劃。
- 身份認證機構可以頒發(fā)兩個東西給用戶,作為身份認證的輸入:機密信息或證明材料。
- 身份認證機構可以通過對比用戶提交和機構保存的機密信息來判斷用戶身份。
- 身份認證機構可以通過檢查和對比用戶提交的證明材料來判斷用戶身份。
- 如果需要,身份認證機構可能會附加別的驗證來增加認證可信度。
- 用戶可以變更機密信息,避免冒用。
- 用戶可以掛失證明材料,使證明材料失效,避免冒用。
身份認證中的機密信息在 Web 環(huán)境中通常以用戶名和密碼的形式存在。由于 HTTP 協(xié)議沒有“狀態(tài)”的概念,所以對于 Web 服務器來說,每次請求都是全新的體驗,都必須驗明請求者的正身。要做到這一點,客戶端可以在每次請求的時候都附上用戶名和密碼(或者別的憑據(jù)),表明身份。
可是,每次都發(fā)送用戶名和密碼增加了泄露風險,所以在第一次驗明正身(登錄)之后,服務器可以發(fā)給調用者一個“令牌”(Token)。這樣,后端的后續(xù)身份認證,無外乎就是把令牌換成“身份”(Identity)。這個令牌實際上就是前面說的證明材料。
我們應該盡量讓令牌不容易仿造,但是技術上無法做到完全杜絕。所以,在敏感操作的時候可能會附加一些別的驗證,比如再次輸入密碼或者用短信驗證碼做二次校驗,這也就是前面所說的附加驗證。
權限驗證
和身份認證相比,權限驗證(Authorization)要復雜一些。
身份認證的輸入,要么是用戶名和密碼(或別的身份憑據(jù)),要么是令牌,只需要通過一個檢查,就能輸出身份信息。而權限的驗證要檢查的是“某用戶能不能做某事情”,所以,至少需要有兩個輸入:“用戶身份”和“想要執(zhí)行的動作”。除了這兩個輸入之外,還需要有一個具體的“判斷規(guī)則”,驗證者才能根據(jù)規(guī)則,輸出“同意”或者“拒絕”。
在現(xiàn)實中,這個判斷規(guī)則有很多種可能。
- 在等級森嚴的軍隊里面,所有的動作和文檔都有明確的“查閱級別”,而每個人也有自己的“查閱級別”。只有用戶的級別高于動作的級別,才能執(zhí)行這個動作。
- 在分工明確的工廠里面,每個人都只負責自己的工作,那么,所有的動作和資源都按照不同的工種來進行分配。各工種只能執(zhí)行屬于自己負責范圍的動作,獲取屬于自己負責范圍的資源。
- 在架構明確的公司里面,每個人都屬于公司行政架構中的一個節(jié)點,可以執(zhí)行屬于這個節(jié)點的動作,并且訪問這個節(jié)點及其下屬節(jié)點的信息。
在專家主導的醫(yī)院里面,所有人都圍繞專家的需要服務,而專家則為病人服務(執(zhí)業(yè))。根據(jù)專家的需要,同時保護敏感信息,我們可能會設置更加復雜的判斷規(guī)則,比如根據(jù)時間段、服務流程階段等來判斷,或者提供一個特定的委托授權的流程用于臨時開放權限。
不管怎么變,只要有了身份、動作和規(guī)則,我們就能做判斷。當然,如果規(guī)則要求我們核實部分數(shù)據(jù),我們還需要這部分的數(shù)據(jù)作輸入。不過,由誰來執(zhí)行這個判斷比較合適,值得我們探討一下。
舉一個生活中的例子。
有這么一家公司,在 A 市有個辦公室,辦公室有個戴經(jīng)理。戴經(jīng)理有一天興之所至,想起來要查一下員工老王的工資。他來到了 HR 部門,找到了 HR 主管,想要調老王的工資出來看看。
HR 看了看公司規(guī)定,經(jīng)理只能看自己所轄辦公室的員工工資,然后又看了看戴經(jīng)理,是負責成都的,再看看老王,是成都員工,然后,就把老王的工資調出來給了戴經(jīng)理。戴經(jīng)理看了,然后說,再給我看看老陳的工資啊。然后 HR 調出檔案一看,老陳是北京辦公室的,就拒絕了。
又有一天,員工老王也興之所至,想要查一下戴經(jīng)理的工資。他也來到 HR 部門,找到 HR 主管,問戴經(jīng)理的工資。HR 一看,你這不是經(jīng)理啊,怎么能查別人工資呢,就直接拒絕了。
如果看這個例子,我們就會發(fā)現(xiàn),這個規(guī)則的檢查是 HR 做的。實際上,絕大部分非 IT 的業(yè)務流程中,權限的檢查都由信息的保管方來執(zhí)行。
我們當然也可以按照這個來建模,但是稍等,再深入分析一下。
- 首先,“誰能看誰的工資”這個規(guī)則,是不是 HR 部門來決定的呢?不是。公司的規(guī)章制度決定了“誰能看誰的工資”,規(guī)章制度由公司管理者制定。
- 然后,當公司制度需要調整的時候,是不是由 HR 部門來調整呢?不是。 還是由管理者來制定,然后由各個部門來執(zhí)行。各個部門實際上是收到了制度調整的結果,而不是自己去調整制度。
- 最后,“誰能看誰的工資”這個規(guī)則,是不是 HR 的專業(yè)范圍?不是。只有“調出工資檔案”這個動作是 HR 的專業(yè)范圍,至于“誰能看”,其實跟 HR 的專業(yè)知識沒有直接關系。
要理解最后這一點,我們可以看兩個場景。
- 某 HR 換了一家新公司?,F(xiàn)在這個新的公司很有意思,允許所有人看所有人的工資,層級也不同,規(guī)章完全不同,但 HR 仍然可以按照自己的專業(yè)來工作不受影響。不只是 HR,對其他部門的人來說也是如此。規(guī)章制度的變化,對它們的職責沒有實質的影響。
- 某 HR 換了一家新公司。這家公司專門搞高精尖的研究,對于員工的信息和商業(yè)機密控制極其嚴格。只要有人來調取數(shù)據(jù),都必須經(jīng)過專門的審核人員審核放行。這對 HR 的職責也沒有實質影響,只要能通過審核,照辦即可。審核人員也不知道 HR 具體的工作是什么,只知道規(guī)則要求檢查什么,就去檢查什么。
總結就是以下幾點。
- 專業(yè)知識(領域邏輯、業(yè)務規(guī)則)和權限是相對獨立的東西。
- 要運用專業(yè)知識,不需要知道權限。
- 要檢查權限,不需要用到專業(yè)知識。
既然如此,為什么在現(xiàn)實中還是由專業(yè)人士來兼職檢查權限呢?這也許是因為對于很多公司來說,絕大部分的數(shù)據(jù)都沒有那么敏感,所以為了降低管理成本,絕大部分的數(shù)據(jù)訪問都沒有那么嚴格地用專職人員去檢查,而是由專業(yè)人士代勞。
了解了這些之后,我們就可以開始規(guī)劃了。
- 權力機構會制定一套權限規(guī)則,并且可能調整這套規(guī)則。
- 這套規(guī)則可能會用到一些外部的輸入,比如員工所在的辦公室。
- 有了這套規(guī)則和查驗數(shù)據(jù)的權力,任何人都可以判斷一個動作是否合規(guī)。
這樣做的好處,就是業(yè)務變得非常純粹,而權限相關的東西完全挪出業(yè)務層面,即便業(yè)務或者權限需要頻繁變化,問題也不大。
說到這里,也順便拋一個待驗證的設想:不同公司的業(yè)務邏輯總是高度雷同,差別最大(妨礙復用)的其實是公司的管理體系。我們把組織結構和與之相關的安全權限單獨拎出來,也許可以更好地促進業(yè)務邏輯的復用。
鑒權服務
為了提升服務的效率,我們一般會希望盡早地做完身份認證和權限驗證。如果用戶執(zhí)行了越權操作,那我們應該及早中止訪問并返回錯誤提示。
前面提到,權限驗證的輸入之一是用戶身份,所以身份認證和權限驗證通常是前后腳來做。二者組合,形成鑒權服務(Auth Service)。鑒權服務負責維護令牌身份映射以及權限規(guī)則,它的輸入是“令牌”和“想執(zhí)行的動作”,輸出是“身份”和“是否允許執(zhí)行”。
幾個例子
現(xiàn)在,我們用這樣一個場景來實驗一下整個鑒權流程。
設有這么一個訂單管理系統(tǒng),其中有一個訂單查詢功能。其權限要求如下。
- 買家只能看到自己下的訂單
- 賣家只能看到下給自己的訂單
- 賣家管轄多個小二,小二可以分組給不同的權限,有的只能看分配給自己的訂單,有的可以查看分配到自己組的訂單
- 運營商可以看到所有訂單
要對這個權限體系進行建模,我們必須認識到,這些操作,雖然查看的都是訂單,但是因為是不同的業(yè)務上下文,表現(xiàn)到 API 呈現(xiàn)上也會有不同。
- 買家看自己的訂單:/CustomerViewOrders
- 賣家看自己的訂單:/MerchantViewOrders
- 運營商看任意訂單:/AdminViewOrders
然后,我們可以制訂如下規(guī)則。
- 所有這些 API 都要求用戶處于已登錄狀態(tài)
- 對于 /CustomerViewOrders ,訪問者必須有 customer 身份
- 對于 /MerchantViewOrders ,訪問者必須有 merchant 身份
- 對于 /AdminViewOrders,要求當前用戶必須有 admin 身份
這樣,鑒權服務就可以根據(jù)身份、動作和規(guī)則三者來判斷訪問權限了。
(圖片來自:http://t.cn/RHRujGG)
至于賣家給小二的權限分配,根據(jù)不同需要,我們可以選擇兩個方案。第一是讓賣家自己去處理這個細粒度的權限,形成自己的一套小的權限體系,這也意味著小二訪問的可能是因賣家中轉而暴露出來的新 API。第二是把這個細粒度的權限也建模到原來的權限體系里面,加入如下新的 API 和判斷規(guī)則。
小二查看訂單: /ClerkViewOrders,檢查:
- 用戶必須是 clerk 身份
- 用戶在組織結構上必須屬于某個 merchant
- 如果用戶類別是 1,那么他可以查看所有分配到自己組內任意小二的訂單
- 如果用戶類別是 2,那么他可以查看分配給自己的訂單
我們再來看另一個場景,查看員工信息。API 的和規(guī)則的設計如下。
- 所有 API 要求用戶處于登錄狀態(tài)
員工查看自己的信息:/EmployeeViewOwnProfile(所有員工均可訪問)
員工查看其他員工的信息:/EmployeeViewProfile(所有員工均可訪問)
經(jīng)理查看員工信息:/ManagerViewProfile(當前用戶必須為 manager 角色、請求中的員工必須屬于該 manager 負責的 location)
不同的 API 返回的數(shù)據(jù)可能有差別,比如看自己的信息可以看全,看別人的只能看名字、照片和聯(lián)系方式,經(jīng)理則可以看所有人的完整信息,這由應用邏輯決定。
(圖片來自:http://t.cn/RHRu1e3)
再來看一個醫(yī)院的。醫(yī)院有一點不同的是,病人和病歷實際上需要在多個部門之間周轉,而不同的角色處在不同部門的時候,其職能和權限會有變化。比如, 有時候實習醫(yī)生會守急診室,住院醫(yī)生不在的時候護士也需要代理執(zhí)行醫(yī)囑,職工可能會輪崗到不同部門,等等。
基于這樣一些假想場景,我們可能會有如下一些 API 和權限。
(1) 掛號處,要求用戶必須有 clerk 身份
- 建檔:/RegistrationsCreateMedicalRecord
- 掛號:/RegistrationsCreateVisit
- 查看病歷(用于確認病人已建檔):/RegistrationViewMedicalRecord
(2) 門診部,要求用戶必須有 doctor 身份
- 診斷:/OutPatientCreateDiagnosis
- 開藥:/OutPatientCreatePrescription
- 查看病歷:/OutPatientViewMedicalRecord
(3) 急診室,要求用戶必須有 doctor 身份
- 查看病歷:/EmergencyViewMedicalRecord
(4) 住院部,要求用戶必須有 doctor 或者 nurse 身份
a.入院:/InPatientAdmitPatient
- (僅 nurse 可以執(zhí)行入院)
b.日常檢查記錄:/InPatientCreateRoutineRecord
- doctor 只能給自己分管的病人創(chuàng)建檢查記錄
- nurse 只能給自己負責區(qū)域的病人創(chuàng)建檢查記錄
c.創(chuàng)建醫(yī)囑:/InPatientCreateOrder
- doctor 只能給自己分管的病人創(chuàng)建醫(yī)囑
- nurse 不能創(chuàng)建醫(yī)囑
d.出院:/InPatientDismissPatient
- 僅 nurse 可以執(zhí)行出院
e.查看病歷:/InPatientViewMedicalRecord
- doctor 只能查看自己分管病人的病歷
(5) 手術室,要求用戶必須有 doctor-surgeon 身份
- 準備材料:/OpRoomPrepareMaterial
- 記錄結果:/OpRoomCreateOpRecord
(6) 檢查部,要求用戶必須有 technician 身份
- 錄入結果:/LabsCreateExaminationRecord
- 查看病歷:/LabsViewMedicalRecord
(7) 藥房,要求用戶必須有 pharmacist 身份
- 看處方:/PharmacyViewPrescription
- 放藥:/PharmacyDeliverMedicine
上述 API 能訪問到的數(shù)據(jù)和權限主要根據(jù)部門來進行劃分,方便輪崗。比如,醫(yī)生在門診的時候,可以查看完整的病人病歷,但輪崗到掛號處的時候,雖然也查看病歷,但就只能查看最基本的個人信息了,用于給病人補辦卡片之類。
功能和數(shù)據(jù)權限
從上面幾個例子看來,我們通??梢园褭嘞薜尿炞C分成兩個步驟:先確定職能,然后確定職能作用范圍。
比如,先確定你能看訂單,然后確定你能看哪些訂單;先確定你能看工資,然后確定你能看誰的工資。再比如,某國法律規(guī)定,當一個案件發(fā)生在某地,警察來調查,但只有該轄區(qū)的警察有調查權,跨區(qū)域的案件必須交給聯(lián)邦警察。如此等等。
既然這兩步看上去分得很清楚,那么我們不妨給它們分別取名。用戶能不能執(zhí)行某個動作,使用某個功能,是功能權限,而能不能在某個數(shù)據(jù)上執(zhí)行該功能(訪問某部分數(shù)據(jù)),是數(shù)據(jù)權限。
促成這種拆分方式的原因可能有下面幾種。
- 現(xiàn)實中,很多組織采取了這種“職能 + 組織節(jié)點”的形式來確定權限,所以這樣的拆分實際上為建模提供了方便。
- 由于功能權限通常會直接對應應用的 API 列表,所以權限驗證可以及早失敗,而無需把數(shù)據(jù)取出來做對比,提升了鑒權的效率。
- 方便我們把所有的功能 API 提取出來形成一個列表或者表格,可以更好地查看和管理權限。
此外,這種形式的權限管理還可以讓業(yè)務人員在不寫代碼的情況下對功能權限進行重新分配。如果涉及數(shù)據(jù)權限,則必然會有某種形式的判斷邏輯,寫代碼也就必不可少了。
話說回來,盡管這種拆分很常見,我們仍應該認識到這只是人為的一種拆分。二者都是權限驗證的一部分,都是為了回答“該用戶能不能做某件事”這個問題,本質沒變。
需要注意的是,在制定權限規(guī)則時,制訂者需要參考業(yè)務規(guī)則,但是反之則不然。業(yè)務規(guī)則可以在完全不了解權限驗證規(guī)則的情況下執(zhí)行。甚至,從理論上說,所有的業(yè)務單元都應該可以在完全沒有權限驗證的情況下“正常裸奔”,即假設所有人可以做所有事情,但業(yè)務應該被正常執(zhí)行,業(yè)務規(guī)則應該被正常遵守。用語言學的詞匯來說,就是在沒有權限驗證的情況下,業(yè)務數(shù)據(jù)中也許會有語義問題(semantic problem),但是不會有句法錯誤(syntax error)。
鑒權體系回顧
我們來回顧一下這篇文章中提到的鑒權體系。
- 身份認證。確認“你是你”,獲取你的身份信息。
- 權限驗證。確認“你能做某件事”。
二者合稱為“鑒權”。身份認證輸入令牌,輸出身份。權限驗證輸入身份、動作(包括動作范圍),輸出“同意”或“拒絕”。我們希望身份和權限在一個體系內高度一致,所以,鑒權是一個半中心化的行為,權限規(guī)則在一個體系(比如組織、應用)內是中心化管理的。
權限的形成需要對業(yè)務知識的了解,但規(guī)則抽象出來之后,要使用它就不需要業(yè)務知識了。權限驗證的獨立,意味著我們把“權限規(guī)則”和“業(yè)務規(guī)則”拆成了兩個部分。前者擁抱變化,而后者追求穩(wěn)定;前者在意的是業(yè)務的意義,后者在意的是業(yè)務的邏輯。
為了適應現(xiàn)有組織形態(tài)和更清晰地展示權限信息,在給權限建模的時候我們常常會把它拆分成功能和數(shù)據(jù)權限兩種。我們應該認識到二者都是權限驗證的一部分,都是為了回答同一個問題:這個用戶能不能做某事。
從整個分析脈絡我們可以看到,這個鑒權體系是通用的。在設計任意一個系統(tǒng)的過程中,我們都應該注意盡量把安全相關的判斷和業(yè)務規(guī)則拆開對待,方便集中管理權限,把業(yè)務規(guī)則提純。
對于微服務架構來說,鑒權是一個重要的節(jié)點,它和應用場景密切結合,是安保的最后一道關口。在對權限進行建模的時候,我們應該尤其謹慎。希望這篇文章能給大家一些啟示。
【本文是51CTO專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號:思特沃克,轉載請聯(lián)系原作者】