使用OPA實現Spring安全授權 | baeldung
在本教程中,我們將展示如何將 Spring Security 的授權決策外部化到 OPA——開放策略代理。
跨應用程式的一個共同要求是能夠根據策略做出某些決定。當這個策略足夠簡單並且不太可能改變時,我們可以直接在程式碼中實現這個策略,這是最常見的場景。
但是,在其他情況下,我們需要更大的靈活性。訪問控制決策是典型的:隨著應用程式變得越來越複雜,授予對給定功能的訪問許可權可能不僅取決於您是誰,還取決於請求的其他上下文方面。這些方面可能包括 IP 地址、時間和登入身份驗證方法(例如:“記住我”、OTP)等。
此外,將上下文資訊與使用者身份相結合的規則應該易於更改,最好不會導致應用程式停機。這一要求自然會導致一個專用服務處理策略評估請求的架構。
這種靈活性的代價是在呼叫外部服務時增加了複雜性和效能損失。另一方面,我們可以在不影響應用程式的情況下發展甚至完全替換授權服務。此外,我們還可以與多個應用程式共享這個服務,從而在它們之間實現一致的授權模式。
什麼是OPA
開放政策代理,簡稱OPA,是一個用Go開源實現的策略評估引擎。它最初是由Styra開發的,現在是CNCF的一個畢業專案。下面是這個工具的一些典型用途的清單。
- Envoy授權過濾器
- Kubernetes准入控制器
- Terraform計劃評估
安裝OPA是非常簡單的。只要下載適合我們平臺的二進位制檔案,把它放在作業系統PATH的一個資料夾裡,就可以了。我們可以用一個簡單的命令來驗證它是否正確安裝。
$ opa version Version: 0.39.0 Build Commit: cc965f6 Build Timestamp: 2022-03-31T12:34:56Z Build Hostname: 5aba1d393f31 Go Version: go1.18 Platform: windows/amd64 WebAssembly: available |
OPA評估用REGO編寫的策略,REGO是一種經過最佳化的宣告性語言,用於執行對複雜物件結構的查詢。這些查詢的結果然後由客戶應用程式根據具體的使用情況來使用。在我們的案例中,物件結構是一個授權請求,我們將使用策略來查詢結果,以授予對特定功能的訪問。
需要注意的是,OPA的策略是通用的,不以任何方式與表達授權決策相聯絡。事實上,我們可以在其他傳統上由Drools等規則引擎主導的場景中使用它。
編寫策略
這是用REGO編寫的一個簡單的授權策略的樣子:
package baeldung.auth.account # Not authorized by default default authorized = false authorized = true { count(deny) == 0 count(allow) > 0 } # Allow access to /public allow["public"] { regex.match("^/public/.*",input.uri) } # Account API requires authenticated user deny["account_api_authenticated"] { regex.match("^/account/.*",input.uri) regex.match("ANONYMOUS",input.principal) } # Authorize access to account allow["account_api_authorized"] { regex.match("^/account/.+",input.uri) parts := split(input.uri,"/") account := parts[2] role := concat(":",[ "ROLE_account", "read", account] ) role == input.authorities[i] } |
首先要注意的是包的宣告。OPA策略使用包來組織規則,它們在評估傳入的請求時也起著關鍵作用,我們將在後面展示。我們可以在多個目錄下組織策略檔案。
接下來,我們定義實際的策略規則。
- 一個預設的規則,以確保我們最終總會得到一個授權變數的值
- 主聚合器規則,我們可以理解為 "當沒有拒絕訪問的規則和至少有一個允許訪問的規則時,授權為真"
- 允許和拒絕規則,每一條都表達了一個條件,如果匹配,將分別在允許或拒絕陣列中增加一個條目。
對OPA策略語言的完整描述超出了本文的範圍,但規則本身並不難讀。在看這些規則時,有幾件事要記住。
- 形式為a :=b或a=b的語句是簡單的賦值(不過它們不一樣)。
- a = b { ......conditions }或a { ......conditions }形式的語句意味著 "如果條件為真,則將b分配給a”
- 在策略檔案中的順序出現是不相關的
除此之外,OPA還有一個豐富的內建函式庫,為查詢深度巢狀的資料結構進行了最佳化,同時還有更多熟悉的功能,如字串操作、集合等等。
評估策略
讓我們使用上一節中定義的策略來評估一個授權請求。在我們的例子中,我們將使用一個包含傳入請求的一些片段的JSON結構來建立這個授權請求。
{ "input": { "principal": "user1", "authorities": ["ROLE_account:read:0001"], "uri": "/account/0001", "headers": { "WebTestClient-Request-Id": "1", "Accept": "application/json" } } } |
請注意,我們已經將請求屬性包裝在一個單一的輸入物件中。這個物件在策略評估過程中成為輸入變數,我們可以用類似JavaScript的語法來訪問它的屬性。
為了測試我們的策略是否像預期的那樣工作,讓我們在本地以伺服器模式執行OPA,並手動提交一些測試請求。
$ opa run -w -s src/test/rego
選項-s可以在伺服器模式下執行,而-w可以自動過載規則檔案。src/test/rego是包含我們樣本程式碼中策略檔案的資料夾。一旦執行,OPA將在本地埠8181監聽API請求。如果需要,我們可以使用-a選項改變預設埠。
現在,我們可以使用curl或其他工具來傳送請求。
$ curl --location --request POST 'http://localhost:8181/v1/data/baeldung/auth/account' \ --header 'Content-Type: application/json' \ --data-raw '{ "input": { "principal": "user1", "authorities": [], "uri": "/account/0001", "headers": { "WebTestClient-Request-Id": "1", "Accept": "application/json" } } }' |
注意/v1/data字首後面的路徑部分。它與策略的包名相對應,點被正斜線取代。
響應將是一個JSON物件,包含針對輸入資料評估策略所產生的所有結果。
{ "result": { "allow": [], "authorized": false, "deny": [] } } |
結果屬性是一個包含由策略引擎產生的結果的物件。我們可以看到,在這種情況下,授權屬性是假的。我們還可以看到,allow 和 deny 是空陣列。這意味著沒有特定的規則與輸入相匹配。因此,主要的授權規則也沒有匹配。
Spring Authorization Manager整合
現在我們已經看到了OPA的工作方式,我們可以繼續前進,把它整合到Spring授權框架中。在這裡,我們將專注於它的反應式Web變體,但一般的想法也適用於基於MVC的常規應用。
首先,我們需要實現ReactiveAuthorizationManager Bean,它使用OPA作為其後臺。
@Bean public ReactiveAuthorizationManager<AuthorizationContext> opaAuthManager(WebClient opaWebClient) { return (auth, context) -> { return opaWebClient.post() .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .body(toAuthorizationPayload(auth,context), Map.class) .exchangeToMono(this::toDecision); }; } |
在這裡,注入的WebClient來自另一個Bean,我們從@ConfigurationPropreties類中預先初始化其屬性。
處理管道委託給toAuthorizationRequest方法的職責是從當前的Authentication和AuthorizationContext中收集資訊,然後建立一個授權請求的有效載荷。同樣地,toAuthorizationDecision也會獲取授權響應,並將其對映為一個AuthorizationDecision。
現在,我們使用這個Bean來構建一個SecurityWebFilterChain:
@Bean public SecurityWebFilterChain accountAuthorization(ServerHttpSecurity http, @Qualifier("opaWebClient") WebClient opaWebClient) { return http .httpBasic() .and() .authorizeExchange(exchanges -> { exchanges .pathMatchers("/account/*") .access(opaAuthManager(opaWebClient)); }) .build(); } |
我們只將我們自定義的AuthorizationManager應用於/account API。這種方法背後的原因是,我們可以很容易地擴充套件這個邏輯以支援多個策略檔案,從而使它們更容易維護。例如,我們可以有一個配置,使用請求URI來選擇一個合適的規則包,並使用這些資訊來建立授權請求。
在我們的案例中,/account API本身只是一個簡單的控制器/服務對,它返回一個用假餘額填充的賬戶物件。
測試
最後但並非最不重要的是,讓我們建立一個整合測試,把所有東西放在一起。首先,讓我們確保 "快樂路徑 "的工作。這意味著,給定一個認證的使用者,他們應該能夠訪問自己的賬戶。
@Test @WithMockUser(username = "user1", roles = { "account:read:0001"} ) void testGivenValidUser_thenSuccess() { rest.get() .uri("/account/0001") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus() .is2xxSuccessful(); } |
其次,我們還必須驗證,一個經過認證的使用者應該只能訪問他們自己的賬戶。
@Test @WithMockUser(username = "user1", roles = { "account:read:0002"} ) void testGivenValidUser_thenUnauthorized() { rest.get() .uri("/account/0001") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus() .isForbidden(); } |
最後,讓我們也測試一下認證使用者沒有許可權的情況。
@Test @WithMockUser(username = "user1", roles = {} ) void testGivenNoAuthorities_thenForbidden() { rest.get() .uri("/account/0001") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus() .isForbidden(); } |
我們可以從 IDE 或命令列執行這些測試。請注意,無論哪種情況,我們都必須首先啟動指向包含我們的授權策略檔案的資料夾的 OPA 伺服器。
結論
在本文中,我們展示瞭如何使用 OPA 將基於 Spring Security 的應用程式的授權決策外部化。像往常一樣,完整的程式碼可以在 GitHub 上找到。
相關文章
- Spring Authorization Server 實現授權中心SpringServer
- Shiro實現使用者授權
- 深入淺出:使用Java和Spring Security實現認證與授權JavaSpring
- Spring Security中實現微信網頁授權Spring網頁
- Istio安全-授權(實操三)
- Spring Boot 整合 Shiro實現認證及授權管理Spring Boot
- 如何在Spring中使用JobRunr實現後臺作業? - BaeldungSpring
- 從零開始學Spring Boot系列-整合Spring Security實現使用者認證與授權Spring Boot
- 【認證與授權】Spring Security的授權流程Spring
- Keycloak中授權的實現
- Spring Security方法級別授權使用介紹Spring
- express基於JWT實現使用者登陸授權ExpressJWT
- Laravel + JWT 實現 API 跨域授權LaravelJWTAPI跨域
- Paypal授權登入流程及實現
- 如何使用PHP進行OAuth2授權流程的實現PHPOAuth
- 客服系統配置抖音開放平臺,實現授權登入回覆私信和評論 實現授權登入,為授權使用者管理回覆私信和評論
- Spring Boot Security OAuth2 實現支援JWT令牌的授權伺服器Spring BootOAuthJWT伺服器
- 九、Spring Boot整合Spring Security之授權概述Spring Boot
- 用於安全授權的DevSecOpsdev
- SpringSecurity(1)---認證+授權程式碼實現SpringGse
- 認證授權的設計與實現
- PHP實現支付寶小程式使用者授權的工具類PHP
- Spring Security OAuth2.0認證授權三:使用JWT令牌SpringOAuthJWT
- 如何實現使用者通訊授權的可信、可知、可追溯?——通訊授權服務技術解讀
- 授權|取消授權MYSQL資料庫使用者許可權MySql資料庫
- Spring Cloud實戰系列(九) - 服務認證授權Spring Cloud OAuth 2.0SpringCloudOAuth
- 瞭解如何使用JSON Web令牌(JWT)實現訪問授權驗證JSONWebJWT
- Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權SpringCloudGatewayOAuthJWT微服務
- ajax 實現微信網頁授權登入網頁
- golang 基於 jwt 實現的登入授權GolangJWT
- 代理ip的授權使用
- Java中實現執行緒安全HashSet的幾種方法 | baeldungJava執行緒
- Spring Security系列之授權過程(七)Spring
- 使用java操作ranger,hdfs ranger授權操作,hive ranger授權操作JavaRangerHive
- Spring Security OAuth2.0認證授權四:分散式系統認證授權SpringOAuth分散式
- 十、Spring Boot整合Spring Security之HTTP請求授權Spring BootHTTP
- 使用Spring Data建立只讀儲存庫 | BaeldungSpring
- 第三方微信登入 | 靜默授權與網頁授權的實現網頁