Zuul 安全處理:結合 OAuth2 和 JWT

Spring Security
Remote
1
04:17 AM · Nov 30 ,2025

1. 簡介

簡單來説,微服務架構允許我們將系統和我們的API分解為一組自包含的服務,這些服務可以完全獨立部署。

雖然從持續部署和管理方面來看這一點很好,但當涉及到API可用性時,可能會變得非常複雜。 隨着不同的端點需要管理,依賴的應用將需要管理CORS(跨域資源共享)以及一組不同的端點。

Zuul是一個邊緣服務,它允許我們將傳入的HTTP請求路由到多個後端微服務。 首先,這對於為我們後端資源的消費者提供統一API至關重要。

基本上,Zuul允許我們通過在它們前面作為代理來統一所有服務。 它接收所有請求並將其路由到正確的服務。 對外部應用程序而言,我們的API看起來像一個統一的API表面。

在本教程中,我們將討論如何用於此目的,並結合OAuth 2.0和JWT,作為保護我們Web服務的前線。 尤其是,我們將使用密碼授權流程來獲取訪問受保護資源的訪問令牌。

請注意,我們僅使用密碼授權流程來探索一個簡單的場景;在生產場景中,大多數客户端更有可能使用授權流程。

2. 添加 Zuul Maven 依賴項

首先,我們添加 Zuul 到我們的項目。我們通過添加 spring-cloud-starter-netflix-zuul 構件來實現:


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

3. 啓用 Zuul

我們想要通過 Zuul 路由的應用程序包含一個 OAuth 2.0 授權服務器,該服務器頒發訪問令牌,以及一個接受這些令牌的資源服務器。這兩個服務分別位於兩個單獨的端點上。

我們希望為這些服務的所有外部客户端提供一個單一的端點,並使用不同的路徑分支到不同的物理端點。為此,我們將 Zuul 作為邊緣服務引入。

為此,我們將創建一個新的 Spring Boot 應用程序,名為 GatewayApplication。然後,我們將簡單地用 @EnableZuulProxy 註解裝飾該應用程序類,這將會啓動一個 Zuul 實例:

@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

4. 配置 Zuul 路由

在繼續之前,我們需要配置一些 Zuul 屬性。首先,我們需要配置 Zuul 監聽傳入連接的端口。這需要在 /src/main/resources/application.yml 文件中進行:

server:
    port: 8080

現在,我們來配置 Zuul 將會轉發的實際路由。為此,我們需要注意以下服務、它們的路徑以及它們監聽的端口。

身份驗證服務器部署在:http://localhost:8081/spring-security-oauth-server/oauth

資源服務器部署在:http://localhost:8082/spring-security-oauth-resource

身份驗證服務器是一個 OAuth 身份提供者。它旨在向資源服務器提供授權令牌,從而提供一些受保護的端點。

身份驗證服務器向客户端提供訪問令牌,客户端再使用該令牌向資源服務器執行請求,代表資源擁有者執行請求。快速瀏覽 OAuth 術語 將有助於我們理解這些概念。

現在,讓我們為每個服務映射一些路由:

zuul:
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth	 

此時,任何到達 Zuul 上的請求,即 localhost:8080/oauth/**,都將被路由到端口 8081 上的身份驗證服務。任何到 localhost:8080/spring-security-oauth-resource/** 的請求都將被路由到端口 8082 上的資源服務器。

5. 確保 Zuul 外部流量路徑安全

即使我們的 Zuul 邊緣服務現在已正確路由請求,但它沒有進行任何授權檢查。位於 /oauth/* 之後,授權服務器會為每次成功的身份驗證創建一個 JWT。當然,它對匿名用户是可訪問的。

資源服務器——位於 /spring-security-oauth-resource/**,另一方面,始終應使用 JWT 訪問,以確保授權客户端正在訪問受保護的資源。

首先,我們將配置 Zuul 以將 JWT 通過傳遞給在其背後運行的服務。在本例中,這些服務本身需要驗證該令牌。

我們通過添加 sensitiveHeaders: Cookie,Set-Cookie 來實現此目的。

這將完成 Zuul 的配置:

server:
  port: 8080
zuul:
  sensitiveHeaders: Cookie,Set-Cookie
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth

在我們搞定這些之後,我們需要處理邊緣處的授權。目前,Zuul 不會在將 JWT 傳遞給我們的下游服務之前驗證 JWT。這些服務會自行驗證 JWT,但理想情況下,我們希望邊緣服務首先執行此操作,並在它們傳播到架構的更深處之前拒絕任何未授權請求。

讓我們設置 Spring Security 以確保在 Zuul 中檢查授權。

首先,我們需要將 Spring Security 依賴項引入到我們的項目中。我們想要 spring-security-oauth2spring-security-jwt:

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>

現在,讓我們為要通過 ResourceServerConfigurerAdapter 保護的路由配置,擴展 ResourceServerConfigurerAdapter。

@Configuration
@Configuration
@EnableResourceServer
public class GatewayConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(final HttpSecurity http) throws Exception {
	http.authorizeRequests()
          .antMatchers("/oauth/**")
          .permitAll()
          .antMatchers("/**")
	  .authenticated();
    }
}

GatewayConfiguration 類定義了 Spring Security 如何處理通過 Zuul 的傳入 HTTP 請求。在 configure 方法中,我們首先使用 antMatchers 匹配最嚴格的路徑,然後允許匿名訪問通過 permitAll。

所有進入 /oauth/** 的請求都應允許通過,而無需檢查任何授權令牌。這在邏輯上是正確的,因為這是生成授權令牌的路徑。

接下來,我們匹配所有其他路徑,即 /**,並通過調用 authenticated 強制所有其他調用應包含訪問令牌。

6. 配置用於 JWT 驗證的密鑰

現在配置已就位,所有路由到 /oauth/** 的請求將被匿名允許通過,而所有其他請求都需要身份驗證。

不過,我們缺少一件事情,那就是用於驗證 JWT 的實際密鑰。要做到這一點,我們需要提供用於對 JWT 進行簽名(在本例中是對稱的)的密鑰。與其手動編寫配置代碼,不如使用 spring-security-oauth2-autoconfigure

讓我們首先將該 Artifact 添加到我們的項目:

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

接下來,我們需要在我們的 application.yaml 文件中添加幾行配置,以定義用於對 JWT 進行簽名的密鑰:

security:
  oauth2:
    resource:
      jwt:
        key-value: 123

該行 key-value: 123 設置了授權服務器用於對 JWT 進行簽名的對稱密鑰。此密鑰將由 spring-security-oauth2-autoconfigure 用於配置令牌解析。

請注意,在生產系統中,我們不應使用在應用程序源代碼中指定的對稱密鑰。 這自然需要外部配置。

7. 測試邊緣服務

7.1. 獲取訪問令牌

現在,讓我們測試我們的 Zuul 邊緣服務如何運行——使用一些 curl 命令。

首先,我們將使用 Authorization Server 獲取新的 JWT,使用 密碼授權

這裏,我們將 用户名和密碼用於獲取訪問令牌。 在這種情況下,我們將 ‘john‘ 作為用户名,並將 ‘123‘ 作為密碼:

curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'Authorization: Basic Zm9vQ2xpZW50SWRQYXNzd29yZDpzZWNyZXQ=' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&password=123&username=john'

此調用會產生一個 JWT 令牌,我們可以將其用於向我們的資源服務器發送身份驗證請求。

請注意 “Authorization: Basic…” 標頭字段。 這用於告知 Authorization Server 連接的客户端。

它對客户端(在本例中為 cURL 請求)就像用户名和密碼對用户一樣:

{    
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "token_type":"bearer",    
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "expires_in":3599,
    "scope":"foo read write",
    "organization":"johnwKfc",
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b"
}

7.2. 測試資源服務器請求

我們可以使用 Authorization Server 獲取的 JWT 現在執行對資源服務器的查詢:

curl -X GET \
curl -X GET \
  http:/localhost:8080/spring-security-oauth-resource/users/extra \
  -H 'Accept: application/json, text/plain, */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Accept-Language: en-US,en;q=0.9' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...' \
  -H 'Cache-Control: no-cache' \

Zuul 邊緣服務將現在在路由到資源服務器之前驗證 JWT。

然後,它會提取 JWT 中的關鍵字段並檢查更細粒度的授權,然後再對請求做出響應:

{
    "user_name":"john",
    "scope":["foo","read","write"],
    "organization":"johnwKfc",
    "exp":1544584758,
    "authorities":["ROLE_USER"],
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b",
    "client_id":"fooClientIdPassword"
}

8. 多層安全保障

需要注意的是,JWT 在通過之前由 Zuul 邊緣服務進行驗證,然後再傳遞到資源服務器。如果 JWT 無效,則請求將在邊緣服務邊界處被拒絕。

另一方面,如果 JWT 確實有效,請求將被傳遞到下游。資源服務器會再次驗證 JWT 並提取關鍵字段,例如用户權限範圍、組織(在本例中為自定義字段)和授權信息。它利用這些字段來決定用户可以執行哪些操作。

簡單來説,在許多架構中,我們可能不需要對 JWT 進行兩次驗證——這是一個您需要根據您的流量模式做出的決定。

例如,在某些生產項目中,單個資源服務器可能直接訪問,也可能通過代理訪問——並且我們可能需要在這兩個地方驗證令牌。而在其他項目中,流量可能僅通過代理進行,此時僅在代理處驗證令牌就足夠了。

9. 總結

正如我們所見,Zuul 提供了一種簡單、可配置的方法來抽象和定義服務路由。 結合使用 Spring Security,它允許我們在服務邊界上進行授權。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.