1. 简介

微服务架构允许我们将系统和 API 拆分为一组自包含的服务,这些服务可以独立部署。

✅ 从持续部署和管理的角度来看,这种架构非常友好
❌ 但从 API 使用角度来看,容易变得复杂:多个接口地址需要客户端分别处理 CORS 和不同接口路径

Zuul 是一个边缘服务(Edge Service),负责将传入的 HTTP 请求路由到多个后端微服务。它为外部应用提供了一个统一的 API 接口,对外隐藏了内部服务的真实结构。

简单来说,Zuul 充当反向代理的角色,接收所有请求并转发给正确的服务。对客户端而言,整个系统看起来像是一个统一的 API。

本文重点介绍如何结合 OAuth2 和 JWT 使用 Zuul 来保护我们的 Web 服务。我们将使用 Password Grant 流程获取访问令牌(Access Token)以访问受保护资源。

⚠️ 注意:这里仅用 Password Grant 举例演示流程,在生产环境中更推荐使用 Authorization Code Grant。

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

假设我们有两个服务:

  • OAuth2 授权服务器(Authorization Server)
  • 受保护资源服务器(Resource Server)

这两个服务分别运行在不同的端口上。

为了统一对外接口,我们创建一个新的 Spring Boot 应用,命名为 GatewayApplication,并在主类上加上 @EnableZuulProxy 注解:

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

这样就启动了一个 Zuul 实例,具备了路由功能。

4. 配置 Zuul 路由规则

接下来我们需要配置 Zuul 的路由映射关系。首先指定 Zuul 监听的端口,写入 /src/main/resources/application.yml 文件中:

server:
  port: 8080

然后配置实际的路由规则:

配置如下:

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

现在访问:

  • http://localhost:8080/oauth/** → 转发到授权服务器(8081)
  • http://localhost:8080/spring-security-oauth-resource/** → 转发到资源服务器(8082)

5. 对外暴露的安全控制

目前 Zuul 已经能正确转发请求,但还没有做任何权限校验。

我们需要让 Zuul 在转发请求前先进行 JWT 校验,避免无效请求进入下游服务。

配置敏感头信息透传

为了让 JWT 正确传递到下游服务,需设置 sensitiveHeaders

zuul:
  sensitiveHeaders: Cookie,Set-Cookie

完整配置如下:

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

引入 Spring Security 依赖

我们需要引入以下两个依赖用于支持 OAuth2 和 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 来配置哪些路径需要认证:

@Configuration
@EnableResourceServer
public class GatewayConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()  // 允许匿名访问授权接口
            .antMatchers("/**").authenticated();   // 其他接口都需要认证
    }
}

解释:

  • /oauth/**:用于获取 token,允许匿名访问
  • /**:其余所有接口都需要携带有效的 JWT

6. 配置 JWT 解密密钥

目前还差一步:用于验证 JWT 的签名密钥。

我们使用 spring-security-oauth2-autoconfigure 自动配置来简化这部分逻辑:

添加自动配置依赖

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

配置密钥

application.yml 中添加密钥配置:

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

⚠️ 生产环境不要硬编码密钥,应通过外部配置或密钥管理系统注入。

7. 测试边缘服务行为

7.1 获取 Access Token

使用 curl 发起请求获取 JWT:

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'

响应示例:

{
    "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 访问资源服务器接口

使用上面获得的 JWT 访问受保护资源:

curl -X GET \
  http://localhost:8080/spring-security-oauth-resource/users/extra \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...' \
  -H 'Cache-Control: no-cache'

响应示例:

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

8. 多层安全验证机制

✅ Zuul 会在请求转发前验证 JWT 的有效性
❌ 如果 JWT 无效,请求不会被转发到下游服务

✅ 若 JWT 有效,则继续转发至 Resource Server,由其再次校验并解析用户权限

⚠️ 是否需要双重验证取决于你的架构设计:

  • 若资源服务器也直接对外暴露,则建议两端都验证
  • 若所有流量都经过 Zuul,则只需在边缘层验证即可

9. 总结

Zuul 提供了灵活、可配置的路由机制,结合 Spring Security 可以轻松实现服务边界的安全控制。

本文展示了如何使用 OAuth2 + JWT + Zuul 构建安全的边缘服务网关,适用于大多数基于微服务的架构。

🔗 完整代码示例请参考 GitHub 仓库


原始标题:Handle Security in Zuul, with OAuth2 and JWT