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
然后配置实际的路由规则:
- 授权服务器地址:http://localhost:8081/spring-security-oauth-server/oauth
- 资源服务器地址:http://localhost:8082/spring-security-oauth-resource
配置如下:
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 仓库