8.3 利用资源服务器保护 API
资源服务器实际上只是一个位于 API 前面的过滤器,确保需要授权的资源包括具有所需范围的有效访问令牌。Spring Security 提供了一个 OAuth2 资源服务器实现,您可以将其添加到现有的 API,将以下依赖项添加到 tacocloud-security 项目的构建中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
您还可以通过选择“OAuth2 Resource Server”来添加资源服务器依赖项,如果创建项目时使用的是 Initialzr。
有了依赖关系后,下一步是声明发送到 /ingredients
的 POST 请求需要“writeIngredients”作用域,并且对 /ingredients
的 DELETE 请求需要“deleteIngredients”作用域。下面摘录的是来自 tacocloud-security 项目的 SecurityConfig 类,显示了如何完成此配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
...
.antMatchers(HttpMethod.POST, "/api/ingredients")
.hasAuthority("SCOPE_writeIngredients")
.antMatchers(HttpMethod.DELETE, "/api//ingredients")
.hasAuthority("SCOPE_deleteIngredients")
...
}
对于每个接口,都会调用 .hasAuthority()
方法来指定所需的作用域。请注意,作用域的前缀为“SCOPE_”,表示它们应该匹配针对请求这些资源时给出的访问令牌中的 OAuth 2 作用域。
在这个配置类中,我们还需要启用资源服务器:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and()
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
...
}
这里为 oauth2ResourceServer()
方法提供了一个 lambda,用于配置资源服务器。在这里,它只是启用 JWT 令牌(与不透明令牌相反),以便资源服务器可以检查令牌的内容,以查找它包含哪些安全声明。具体来说,它将查看令牌是否包括“writeIngredients”和/或
“deleteIngredients”作用域,这是我们所保护的两个接口所适用的作用域。
不过,它不会按设置值直接信任令牌。要确保令牌是由受信的授权服务器生成的,它将使用与创建令牌签名私钥匹配的公钥来进行验证。我们需要配置资源服务器知道从何处获取公钥,请执行以下操作。以下属性将指定资源服务器访问的授权服务器上的 JWK 的 URL,并从那里获取公钥:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9000/oauth2/jwks
现在我们的资源服务器已经准备好了!构建 Taco Cloud 应用程序并启动它。您可以像这样使用 curl 进行尝试:
$ curl localhost:8080/ingredients \
-H"Content-type: application/json" \
-d'{"id":"CRKT", "name":"Legless Crickets", "type":"PROTEIN"}'
请求应该会失败,并返回 HTTP 401 响应码。这是因为我们已经配置了接口需要“writeIngredients”作用域,但我们在请求上尚未提供有效作用域的访问令牌。
要想请求成功并添加新的配料,您需要使用我们在上一节中使用的流,获得相应的访问令牌,确保我们的请求在将浏览器转到授权服务器时具有“writeIngredients”和“deleteIngreients”作用域。然后,像这样使用 curl 在“Authorization”请求头中提供访问令牌 (用“$token”代替实际访问令牌):
$ curl localhost:8080/ingredients \
-H"Content-type: application/json" \
-d'{"id":"SHMP", "name":"Coconut Shrimp", "type":"PROTEIN"}' \
-H"Authorization: Bearer $token"
这一次应该会创建出新的配料。您可以使用 curl 或 HTTP 客户端以执行对接口的 GET 请求:
$ curl localhost:8080/ingredients
[
{
"id": "FLTO",
"name": "Flour Tortilla",
"type": "WRAP"
},
...
{
"id": "SHMP",
"name": "Coconut Shrimp",
"type": "PROTEIN"
}
]
“椰子虾”配料,现在被列在从该工厂返回的所有配料清单的末尾。新建确实成功了!
我们知道访问令牌 5 分钟后就会过期。如果令牌过期,请求会再次返回 HTTP 401 响应码。您可以使用与访问令牌一起使用的刷新令牌,通过向授权服务器发出请求来获取新的访问令牌(将实际刷新令牌替换为“$refreshToken”):
$ curl localhost:9000/oauth2/token \
-H"Content-type: application/x-www-form-urlencoded" \
-d"grant_type=refresh_token&refresh_token=$refreshToken" \
-u taco-admin-client:secret
有了一个新创建的访问令牌,您可以继续为随心所欲的创建新的配料了。
既然我们已经保护了 /ingredients
接口,那么就应该对其他 API 也使用相同的技术来进行保护了。例如,/orders
接口不应该让任何类型的请求打开,即使是 HTTP GET 请求,因为这将允许黑客轻松获取客户信息。我让您自己来处理对其他 API 接口的安全保护工作。
使用 curl 管理 tacocloud 应用程序只适合简单修补,以了解 OAuth 2 令牌是如何在资源保护方面发挥作用的。但最终我们想要的是一个可用于管理配料的真实客户端应用程序。现在我们来关注一下如何创建 OAuth 客户端,该客户端将获取访问令牌并向 API 发出请求。