貓頭鷹的深夜翻譯:對於RestAPI簡單的基於身份的許可權控制
前言
基於角色的許可權控制(RBAC)是管理使用者對某種資源或操作的許可權的通用方法。許可權可以明確指定可以訪問的資源和操作。基本原理如下:許可權將被分配給某個角色,並將該角色分配給某個使用者或者是使用者組,而不是直接分配給某個使用者。
角色與許可權捆綁
將許可權與單個使用者關聯起來是一件很複雜的事情,隨著更多的使用者使用系統,維護使用者的許可權變得更加困難,且容易出錯。許可權的錯誤分配會阻止使用者訪問所需的系統,甚至是允許非授權使用者訪問限制區域或是執行危險操作。
在這篇文章中,我會介紹如何對應用開啟許可權控制。許可權控制的模型有許多種,比如RBAC(基於角色的許可權控制),DAC(自由訪問控制)等。雖然文件中解釋的原則可以應用於各種模型,但我選擇RBAC作為參考,因為它被廣泛接受並且非常直觀。
檢視使用者的活動通常只會產生使用者執行的有限數量的操作(如讀取資料,提交表單)。深入觀察這些使用者的行為會發現,這些行為通常一起執行,即執行A操作的使用者往往也會執行B操作。比如,讀取並更新報告,或者是新增和刪除使用者。這些都可以與角色繫結,比如編輯或是賬戶管理員。注意這裡的角色並不一定和職稱或是組織結構繫結,而是以有意義的方式反映相關的使用者操作。當恰當劃分好角色並分配給使用者時,就可以將許可權分配給每個角色,而非使用者。管理少量角色的許可權是一件相對簡單的事情。
如下,是沒有角色作為中介的許可權與使用者圖:
而如下,則是完全相同的使用者和許可權集,由角色組織:
顯而易見,角色使得許可權管理更容易了
使用者與群組繫結
將使用者與群組繫結是一種更好的實踐。
在觀察使用者關於上述角色的行為模式時,我們經常發現使用者之間有很多共同之處,比如某一組使用者常常行為相似--在共同的資源上執行相同的操作。這允許我們將使用者組織到組中,然後將角色分配給少陣列,而不是許多使用者。比如,會發現一組使用者都需要系統管理員許可權,因此我們新建一個名叫賬戶管理員的群組,將使用者新增到該組並將該角色分配給該組,而不是每個使用者。
實現角色時的注意事項
不要將行為和驗證細節耦合
在許多系統中,開發人員通過直接在實現方法上指定許可權來限制對特定操作的訪問。沒錯,就在程式碼上!通常,角色的驗證通過註解新增到需要檢查的方法上,比如這裡提供了一個spring-security的一個範例:
@PreAuthorize("hasRole('Editor')") public void update_order(Order order);
在不同語言和框架中,這種做法非常常見。雖然很容易實現,但遺憾的是,它在所需角色和動作的實現之間產生了不希望的耦合。想象一下有幾十個方法都需要新增這樣的註解。跟蹤每一個角色的有效操作將會變得很艱難,幾乎肯定會導致依賴於不準確或過時的文件,或者更糟糕的是 - 分散在您的應用程式中的未知,非託管許可權。
從客戶的角度來看,這種耦合使得無法修改開發人員事先定義的角色集或者他們的許可權,因為更改它意味著每次都必須編譯和打包程式碼!這種使用者體驗也許不是我們的目標。
如何避免耦合
更好的方式是,首先從要由外部授權機制處理的程式碼中提取可能的操作列表,然後,我們可以使程式碼不知道角色或任何其他授權細節,簡單地詢問當前使用者(無論它是否被檢索)是否具有執行特定方法所需的許可權(無論在何處定義)。
這允許我們使用更加通用的註解,如下所示:
@Secured public void update_order(Order order);
角色和許可權的對映(即執行特定操作的許可權)現在可以在配置檔案中完成,可以由客戶輕鬆定製!
比如,假設有這樣的一個 roles_config.yml 檔案:
order_manager: - 'create_order' - 'view_order' - 'delete_order' - 'update_order' order_inspector: - 'viewer_order'
由@secured註解的方法回去查詢配置檔案確定當前使用者是否具有執行該操作的許可權。這意味著當前使用者必須具有order_manager的角色,而這一點也是很容易配置的。但是,授權機制必須知道如何將每個許可權與程式碼中的特定方法相匹配,並且有人必須記錄所有可用的方法(即create_order,view_order等)。
關注點分離--外部授權
既然方法實現程式碼不包含授權細節,整個授權邏輯可以移動到單獨的獨立模組。通過使用通用標題(例如註解 @secure
),我們允許修改整個授權機制而不影響應用程式的程式碼。例如,可以將 @secure
實現為基於角色的檢查,但也可以使用訪問控制列表(ACL)。比如,檢查當前使用者是否列在訂單的ACL列表中。另一種解決方案可以是通過詢問第三方是否允許使用者執行該動作來使用oauth。
Rest是最佳選擇
提取操作--舉手之勞
REST介面肯定更好,或者至少是最容易匹配這個模型的。設計良好的Rest服務通過標準的基於HTTP的API暴露資源和方法,資源通過URI定義,方法通過HTTP動詞(如GET,PUT)等定義。
比如, POST http://www.domain.com/bookings
會建立一本新書,而 GET http://www.domain.com/orders/12345
會返回訂單#12345的詳情。這意味著可以輕而易舉的獲得資源的名稱和對資源的操作。
請求閘道器
除了標準的建模操作之外,REST服務通常是請求流中評估身份驗證和授權的好地方,因為這通常是系統的主要入口點。為了使訪問控制機制有意義,建議阻止所有其他到系統的路由,例如直接訪問資料儲存或程式碼中的任何遠端呼叫機制。該架構的另一個重要優點是響應過濾,以防某些不應當返回給使用者的資料寫在響應中。
請求也是訪問控制工具
REST服務處理傳入請求,這意味著請求中找到的資訊可用於制定訪問控制決策。一些有用的細節是:
- 請求源:允許阻止來自不明IP或是網段的請求
- 請求頭:許多有意義的細節可以在請求頭傳遞,比如使用者憑證,從而支援全面的認證/授權過程。
- 目標終端:如請求的URI所示。根據其他條件,訪問可以僅限於應用程式端點的子集。例如,雖然version端點對所有人開放,但secret端點僅對經過身份驗證的使用者開放。
- 目標方法:由HTTP動詞(例如DELETE)表示,這意味著可以基於被呼叫的方法傳遞或阻止請求。
總而言之:用REST來實現許可權控制
所有的資源將會通過REST的URI表示,操作通過HTTP動詞表示,這能夠覆蓋所有能被執行且需要驗證的操作。在下面的例子中,定義了三個角色:
- order_manager:能夠檢視,建立,更新和刪除訂單
- order_editor:能夠檢視,建立,更新訂單,但不能刪除他們
- order_inspector:只能檢視訂單
order_manager: '/orders': - 'GET' - 'POST' - 'PUT' - 'DELETE' order_editor: '/orders': - 'GET' - 'POST' - 'PUT' order_inspector: '/orders': - 'GET'
由此可見,REST天然能夠實現許可權控制。通過處理傳入請求,REST服務能夠檢索有價值的資訊,這些資訊可以移交給單獨的模組以執行身份驗證和授權。如果使用者被授權在目標資源上執行所請求的方法,則可以繼續請求處理。否則,在到達任何內部應用程式程式碼之前拒絕進一步訪問。