# 序言
安全性是暴露由许多微服务组成的公共访问 API 时要考虑的最重要的一个方面。Spring 有一些有趣的功能和框架,使我们的微服务安全配置更容易。在本文中,我将向您展示如何使用 Spring Cloud 和 Oauth2 在 API 网关后面提供令牌访问安全性。
# 理论知识
OAuth2 标准目前被所有主要网站使用并且允许通过共享 API 访问其资源。它是一种开放式授权标准,允许用户将存储在一个页面中的私有资源共享到另一个页面,而无需进入其凭据服务。这些是与 oauth2 相关的基本术语。
- Resource Owner – 处理对资源的访问
- Resource Server – 存储可以使用特殊令牌共享的所有者资源的服务器
- Authorization Server – 管理密钥,令牌和其他临时资源访问代码的分配。它还必须确保授予相关人员访问权限
- Access Token – 允许访问资源的密钥
- Authorization Grant – 授予访问权限。有多种方法可以确认访问权限:授权代码,隐式,资源所有者密码凭据和客户端凭据
您可以在此处以及在 digitalocean 文章中阅读有关此标准的更多信息。该协议的流程主要有三个步骤。首先,我们将授权请求发送给资源所有者。在资源所有者响应后,我们向授权服务器发送授权请求并接收访问令牌。最后,我们将此访问令牌发送到资源服务器,如果它有效,则 API 将资源提供给应用程序。
# 方案
下图显示了我们案例的架构。我们用 API 网关(Zuul)代理我们对授权服务器的请求和两个帐户微服务实例。授权服务器是某种提供 outh2 安全机制的基础结构服务。我们还有发现服务(Eureka),我们所有的微服务都已注册。

# 网关
对于我们的示例,我们不会在 API 网关上提供任何安全性。它只需要将客户端的请求代理到授权服务器和帐户微服务。在下面可见的 Zuul 的网关配置中,我们将 sensitiveHeaders 属性设为空值以启用授权 HTTP 头转发。默认情况下,Zuul 在将我们的请求转发到目标 API 时切断了该头,这是不正确的,因为网关后面的服务需要基本授权。
zuul:
routes:
uaa:
path: /uaa/**
sensitiveHeaders:
serviceId: auth-server
account:
path: /account/**
sensitiveHeaders:
serviceId: account-service
网关源代码中的主类非常简单。它只需要启用 Zuul 代理功能和发现客户端来收集 Eureka 注册表中的服务。
@SpringBootApplication
@EnableZuulProxy
@EnableOAuth2Sso
@EnableDiscoveryClient
@EnableJdbcHttpSession
public class GatewayServer {
public static void main(String[] args) {
SpringApplication.run(GatewayServer.class, args);
}
}
# 授权服务器
授权服务器的主类也很简单。它基于默认的 Spring 安全配置。客户端授权详细信息存储在内存存储库中。当然在生产模式中,您也可以使用其他实现而不是内存存储库,如 JDBC 数据源和令牌存储。您可以在Spring Security Reference (opens new window) 和 Spring Boot Security (opens new window)中阅读有关 Spring 授权机制的更多信息。这是 application.yml 的配置片段。我们为/token端点提供了用户基本身份验证数据和基本安全凭证:client-id和client-secret。用户凭据是普通的 Spring Security 用户详细信息。
security:
user:
name: root
password: password
oauth2:
client:
client-id: acme
client-secret: secret
这是用@EnableAuthorizationServer实现身份验证服务器的主要类。我们公开一个 REST 端点验证帐户服务的用户身份详情,并为客户端启用了 Eureka 注册和发现。
@SpringBootApplication
@EnableAuthorizationServer
@EnableDiscoveryClient
@EnableResourceServer
@RestController
public class AuthServer {
public static void main(String[] args) {
SpringApplication.run(AuthServer.class, args);
}
@RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
}
# Application-账户微服务
我们的示例微服务只有一个 @GET 请求的端点,它始终返回相同的帐户。在主类中,资源服务和 Eureka 发现都已启用。服务配置很简单。 GitHub (opens new window)上提供了示例应用程序源代码。
@SpringBootApplication
@EnableDiscoveryClient
@EnableResourceServer
public class AccountService {
public static void main(String[] args) {
SpringApplication.run(AccountService.class, args);
}
}
security:
user:
name: root
password: password
oauth2:
resource:
loadBalanced: true
userInfoUri: http://localhost:9999/user
# 测试
我们只需要 Web 浏览器和 REST 客户端(例如 Chrome Advanced REST 客户端)来测试我们的解决方案。从向资源所有者发送授权请求开始测试。我们可以通过 Web 浏览器中的 Zuul 网关调用 oauth2 授权端点。
发送请求后,我们应该看到下面的页面。选择“Approve”,然后单击“Authorize”,从授权服务器请求访问令牌。如果应用程序标识已通过身份验证且授权授予有效,则应在 HTTP 响应中返回应用程序的访问标记。

最后一步是使用访问令牌调用帐户端点。我们不得不将其作为承载令牌放入 Authorization 标头中。在示例应用程序中,安全操作的日志记录级别设置为 TRACE,因此您可以轻松找出问题发生时的情况。

# 结束语
说实话,我不太熟悉应用程序中的安全问题。所以对我来说一个非常重要的是使用的安全解决方案的简单性。在 Spring Security 中,我们几乎所有需要的机制都是开箱即用的。它还提供可轻松扩展的组件,以满足更高级的要求。您应该将本文视为使用 Spring Cloud 和 Spring Security 项目的更高级解决方案的简要介绍。
作者:Piotr Mińkowski
译者:Emma