1. Introduction
1.绪论
Simply put, a microservice architecture allows us to break up our system and our API into a set of self-contained services, which can be deployed fully independently.
简单地说,微服务架构允许我们把我们的系统和我们的API分解成一组独立的服务,这些服务可以完全独立部署。
While this is great from a continuous deployment and management point of view, it can quickly become convoluted when it comes to API usability. With different endpoints to manage, dependent applications will need to manage CORS (Cross-Origin Resource Sharing) and a diverse set of endpoints.
虽然从持续部署和管理的角度来看,这很好,但当涉及到API的可用性时,它很快就会变得复杂化。由于要管理不同的端点,依赖性的应用程序将需要管理CORS(跨源资源共享)和一组不同的端点。
Zuul is an edge service that allows us to route incoming HTTP requests into multiple backend microservices. For one thing, this is important for providing a unified API for consumers of our backend resources.
Zuul是一个边缘服务,允许我们将传入的HTTP请求路由到多个后端微服务。首先,这对于为我们后端资源的消费者提供一个统一的API很重要。
Basically, Zuul allows us to unify all of our services by sitting in front of them and acting as a proxy. It receives all requests and routes them to the correct service. To an external application, our API appears as a unified API surface area.
基本上,Zuul允许我们通过坐在它们前面并作为代理来统一我们所有的服务。它接收所有的请求并将它们路由到正确的服务。对于外部应用程序来说,我们的API就像一个统一的API表面区域。
In this tutorial, we’ll talk about how we can use it for this exact purpose, in conjunction with an OAuth 2.0 and JWTs, to be the front line for securing our web services. Specifically, we’ll be using the Password Grant flow to obtain an Access Token to the protected resources.
在本教程中,我们将讨论如何将其与OAuth 2.0 和 JWT结合使用,以达到这一确切目的,从而成为保护我们的 Web 服务的第一线。具体而言,我们将使用密码授予流程来获得受保护资源的访问令牌。
A quick but important note is that we’re only using the Password Grant flow to explore a simple scenario; most clients will more likely be using the Authorization Grant flow in production scenarios.
一个快速但重要的说明是,我们只是使用密码授权流来探索一个简单的场景;大多数客户更可能在生产场景中使用授权授权流。
2. Adding Zuul Maven Dependencies
2.添加Zuul Maven依赖项
Let’s begin by adding Zuul to our project. We do this by adding the spring-cloud-starter-netflix-zuul artifact:
让我们首先将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. Enabling Zuul
3.启用Zuul
The application that we’d like to route through Zuul contains an OAuth 2.0 Authorization Server which grants access tokens and a Resource Server which accepts them. These services live on two separate endpoints.
我们想通过Zuul路由的应用程序包含一个授予访问令牌的OAuth 2.0授权服务器和一个接受令牌的资源服务器。这些服务生活在两个独立的端点上。
We’d like to have a single endpoint for all external clients of these services, with different paths branching off to different physical endpoints. To do so, we’ll introduce Zuul as an edge service.
我们希望为这些服务的所有外部客户提供一个单一的端点,不同的路径分支到不同的物理端点。为了做到这一点,我们将引入Zuul作为边缘服务。
To do this, we’ll create a new Spring Boot application, called GatewayApplication. We’ll then simply decorate this application class with the @EnableZuulProxy annotation, which will cause a Zuul instance to be spawned:
要做到这一点,我们将创建一个新的Spring Boot应用程序,名为GatewayApplication。然后我们将简单地用@EnableZuulProxy注解来装饰这个应用类,这将导致一个Zuul实例被生成。
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
4. Configuring Zuul Routes
4.配置Zuul路线
Before we can go any further, we need to configure a few Zuul properties. The first thing we’ll configure is the port on which Zuul is listening for incoming connections. That needs to go into the /src/main/resources/application.yml file:
在我们进一步行动之前,我们需要配置一些Zuul属性。我们要配置的第一件事是Zuul监听传入连接的端口。这需要进入/src/main/resources/application.yml文件。
server:
port: 8080
Now for the fun stuff, configuring the actual routes that Zuul will forward to. To do that, we need to note the following services, their paths and the ports that they listen on.
现在是有趣的事情,配置Zuul将转发的实际路由。要做到这一点,我们需要注意以下服务,它们的路径和它们所监听的端口。
The Authorization Server is deployed on: http://localhost:8081/spring-security-oauth-server/oauth
授权服务器被部署在。http://localhost:8081/spring-security-oauth-server/oauth
The Resource Server is deployed on: http://localhost:8082/spring-security-oauth-resource
资源服务器被部署在。http://localhost:8082/spring-security-oauth-resource
The Authorization Server is an OAuth identity provider. It exists to provide authorization tokens to the Resource Server, which in turn provides some protected endpoints.
授权服务器是一个OAuth身份提供者。它的存在是为了向资源服务器提供授权令牌,而资源服务器则提供一些受保护的端点。
The Authorization Server provides an Access Token to the Client, which then uses the token to execute requests against the Resource Server, on behalf of the Resource Owner. A quick run through the OAuth terminology will help us keep these concepts in view.
授权服务器向客户提供一个访问令牌,然后客户使用该令牌代表资源所有者对资源服务器执行请求。快速浏览一下OAuth术语将有助于我们保持对这些概念的关注。
Now let’s map some routes to each of these services:
现在,让我们为这些服务中的每一个绘制一些路线。
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
At this point, any request reaching Zuul on localhost:8080/oauth/** will be routed to the authorization service running on port 8081. Any request to localhost:8080/spring-security-oauth-resource/** will be routed to the resource server running on 8082.
此时,任何到达Zuul的请求localhost:8080/oauth/**将被路由到运行在8081端口的授权服务。任何对localhost:8080/spring-security-oauth-resource/**的请求将被路由到运行在8082上的资源服务器。
5. Securing Zuul External Traffic Paths
5.确保Zuul外部流量路径的安全
Even though our Zuul edge service is now routing requests correctly, it’s doing so without any authorization checks. The Authorization Server sitting behind /oauth/*, creates a JWT for each successful authentication. Naturally, it’s accessible anonymously.
尽管我们的Zuul边缘服务现在可以正确地路由请求,但它在这样做时没有进行任何授权检查。坐落在/oauth/*后面的授权服务器,为每个成功的认证创建一个JWT。当然,它是可以匿名访问的。
The Resource Server – located at /spring-security-oauth-resource/**, on the other hand, should always be accessed with a JWT to ensure that an authorized Client is accessing the protected resources.
另一方面,资源服务器–位于/spring-security-oauth-resource/**,应该总是用JWT来访问,以确保授权的客户端正在访问受保护的资源。
First, we’ll configure Zuul to pass through the JWT to services that sit behind it. In our case here, those services themselves need to validate the token.
首先,我们要配置Zuul,使其能够将JWT传递给位于其背后的服务。在我们的案例中,这些服务本身需要验证该令牌。
We do that by adding sensitiveHeaders: Cookie,Set-Cookie.
我们通过添加sensitiveHeaders来做到这一点。Cookie,Set-Cookie。
This completes our Zuul configuration:
这就完成了我们的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
After we’ve got that out of the way, we need to deal with authorization at the edge. Right now, Zuul will not validate the JWT before passing it on to our downstream services. These services will validate the JWT themselves, but ideally, we’d like to have the edge service do that first and reject any unauthorized requests before they propagate deeper into our architecture.
在我们解决了这个问题之后,我们需要处理边缘的授权问题。现在,Zuul在将JWT传递给我们的下游服务之前不会验证它。这些服务将自己验证JWT,但理想的情况是,我们希望边缘服务首先进行验证,并在它们传播到我们的架构深处之前拒绝任何未经授权的请求。
Let’s set up Spring Security to ensure that authorization is checked in Zuul.
让我们设置Spring Security,以确保在Zuul中检查授权。
First, we’ll need to bring in the Spring Security dependencies into our project. We want spring-security-oauth2 and spring-security-jwt:
首先,我们需要将Spring Security的依赖性引入我们的项目。我们需要spring-security-oauth2和spring-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>
Now let’s write a configuration for the routes we want to protect by extending 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();
}
}
The GatewayConfiguration class defines how Spring Security should handle incoming HTTP requests through Zuul. Inside the configure method, we’ve first matched the most restrictive path using antMatchers and then allowed anonymous access through permitAll.
GatewayConfiguration类定义了Spring Security应该如何处理通过Zuul传入的HTTP请求。在configure方法中,我们首先使用antMatchers匹配最严格的路径,然后通过permitAll允许匿名访问。
That is all requests coming into /oauth/** should be allowed through without checking for any authorization tokens. This makes sense because that’s the path from which authorization tokens are generated.
也就是说,所有进入/oauth/**的请求都应该被允许通过,而无需检查任何授权令牌。这是有道理的,因为那是生成授权令牌的路径。
Next, we’ve matched all other paths with /**, and through a call to authenticated insisted that all other calls should contain Access Tokens.
接下来,我们用/**来匹配所有其他路径,并通过对authenticated的调用,坚持认为所有其他调用应该包含访问令牌。
6. Configuring the Key Used for JWT Validation
6.配置用于JWT验证的密钥
Now that the configuration is in place, all requests routed to the /oauth/** path will be allowed through anonymously, while all other requests will require authentication.
现在,配置已经到位,所有路由到/oauth/**路径的请求将被允许匿名通过,而所有其他请求将需要认证。
There is one thing we’re missing here though, and that’s the actual secret required to verify that the JWT is valid. To do that, we need to provide the key (which is symmetric in this case) used to sign the JWT. Rather than writing the configuration code manually, we can use spring-security-oauth2-autoconfigure.
不过我们还缺少一样东西,那就是验证JWT是否有效所需的实际秘密。要做到这一点,我们需要提供用于签署JWT的密钥(在这种情况下是对称的)。我们可以使用spring-security-oauth2-autoconfigure,而不是手动编写配置代码。
Let’s start by adding the artifact to our project:
让我们先把工件添加到我们的项目中。
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
Next, we need to add a few lines of configuration to our application.yaml file to define the key used to sign the JWT:
接下来,我们需要在application.yaml文件中添加几行配置,以定义用于签署JWT的密钥。
security:
oauth2:
resource:
jwt:
key-value: 123
The line key-value: 123 sets the symmetric key used by the Authorization Server to sign the JWT. This key will be used by spring-security-oauth2-autoconfigure to configure token parsing.
这一行key-value: 123设置了授权服务器用来签署JWT的对称密钥。这个密钥将被spring-security-oauth2-autoconfigure用来配置令牌解析。
It’s important to note that, in a production system, we shouldn’t use a symmetric key, specified in the source code of the application. That naturally needs to be configured externally.
值得注意的是,在生产系统中,我们不应该使用应用程序源代码中指定的对称密钥。这自然需要在外部进行配置。
7. Testing the Edge Service
7.测试边缘服务
7.1. Obtaining an Access Token
7.1.获得访问令牌
Now let’s test how our Zuul edge service behaves – with a few curl commands.
现在让我们测试一下我们的Zuul边缘服务是如何表现的–用几个curl命令。
First, we’ll see how we can obtain a new JWT from the Authorization Server, using the password grant.
首先,我们将看到我们如何从授权服务器获得一个新的JWT,使用password grant。
Here we exchange a username and password in for an Access Token. In this case, we use ‘john‘ as the username and ‘123‘ as the password:
在这里,我们用一个用户名和密码来交换一个访问令牌。在这种情况下,我们使用’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'
This call yields a JWT token which we can then use for authenticated requests against our Resource Server.
这个调用产生了一个JWT令牌,然后我们可以用它来对我们的资源服务器进行认证请求。
Notice the “Authorization: Basic…” header field. This exists to tell the Authorization Server which client is connecting to it.
注意“授权。Basic…”头域。它的存在是为了告诉授权服务器哪个客户正在连接它。
It’s to the Client (in this case the cURL request) what the username and password are to the user:
它对客户(在这种情况下是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. Testing a Resource Server Request
7.2.测试一个资源服务器请求
We can then use the JWT we retrieved from the Authorization Server to now execute a query against the Resource 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' \
The Zuul edge service will now validate the JWT before routing to the Resource Server.
Zuul边缘服务现在将在路由到资源服务器之前验证JWT。
This then extracts key fields from the JWT and checks for more granular authorization before responding to the request:
然后从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. Security Across Layers
8.跨越层级的安全
It’s important to note that the JWT is being validated by the Zuul edge service before being passed into the Resource Server. If the JWT is invalid, then the request will be denied at the edge service boundary.
值得注意的是,JWT在被传入资源服务器之前,是由Zuul边缘服务验证的。如果JWT是无效的,那么请求将在边缘服务边界被拒绝。
If the JWT is indeed valid on the other hand, the request is passed on downstream. The Resource Server then validates the JWT again and extracts key fields such as user scope, organization (in this case a custom field) and authorities. It uses these fields to decide what the user can and can’t do.
另一方面,如果JWT确实有效,请求就会被传递到下游。然后,资源服务器再次验证JWT,并提取关键字段,如用户范围、组织(在这种情况下是自定义字段)和权限。它使用这些字段来决定用户可以和不可以做什么。
To be clear, in a lot of architectures, we won’t actually need to validate the JWT twice – that’s a decision you’ll have to make based on your traffic patterns.
说白了,在很多架构中,我们实际上不需要对JWT进行两次验证–这是你必须根据你的流量模式来决定的。
For example, in some production projects, individual Resource Servers may be accessed directly, as well as through the proxy – and we may want to verify the token in both places. In other projects, traffic may be coming only through the proxy, in which case verifying the token there is enough.
例如,在一些生产项目中,个别资源服务器可能被直接访问,也可能通过代理访问–我们可能想在这两个地方验证令牌。在其他项目中,流量可能只通过代理,在这种情况下,验证那里的令牌就足够了。
9. Summary
9.摘要
As we’ve seen Zuul provides an easy, configurable way to abstract and define routes for services. Together with Spring Security, it allows us to authorize requests at service boundaries.
正如我们所看到的,Zuul提供了一种简单、可配置的方式来抽象和定义服务的路由。与Spring Security一起,它允许我们在服务边界对请求进行授权。
Finally, as always, the code is available over on Github.
最后,像往常一样,代码可以在Github上获得。