使用资源服务器保护API
资源服务器实际上就是API前的一个过滤器,它确保对需要授权的资源的请求中包括一个有效的访问令牌和所需scope。Spring Security提供了OAuth2资源服务器的实现,我们可以将其添加到现有的API中。这需要在项目的构建文件中添加如下的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
也可以在创建项目时,通过在Initializr中选择OAuth2 Resource Server将资源服务器依赖添加进来。
依赖准备就绪之后,接下来,我们就是声明对“/ingredients”的POST请求需要“writeIngredients”scope,并且对“/ingredients”的DELETE请求需要“deleteIngredients”scope。如下所示是项目中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。请注意,这里scope的前缀是“SCOPE_”,表明它们应该与请求这些资源时所携带的访问令牌中的OAuth 2 scope进行匹配。
在相同的配置类中,我们还需要启用资源服务器:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and()
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
...
}
这里的oauth2ResourceServer()方法被赋予了一个lambda表达式,借助它,我们可以配置资源服务器。在这里,我们只启用了JWT令牌(与之相对的是opaque令牌),所以资源服务器会探查令牌的内容来确定它包含哪些安全要求(claim)。具体来讲,对于我们保护的两个端点,它将会查看令牌中是否包含“writeIngredients”或“deleteIngredients”scope。
不过,它不会相信令牌中表面上的值。为了确信令牌是由受信任的授权服务器代表用户的身份创建的,它会使用一个公钥校验令牌的签名,这个公钥是与创建令牌签名时所使用私钥配对的。不过,我们需要配置资源服务器从何处获取这个公钥。如下的属性会指定授权服务器上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”scope,但是在请求中,我们并没有提供包含该scope的合法访问令牌。
为了让请求成功并将这个新的配料添加进去,我们需要使用8.2节介绍的流程获取一个访问令牌,确保浏览器在重定向到授权服务器时请求获取“writeIngredients”和“deleteIngredients”的许可。然后,使用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客户端对“/ingredients”端点发送GET请求进行校验:
$ curl localhost:8080/ingredients
[
{
"id": "FLTO",
"name": "Flour Tortilla",
"type": "WRAP"
},
...
{
"id": "SHMP",
"name": "Coconut Shrimp",
"type": "PROTEIN"
}
]
在“/ingredients”端点返回的配料列表的结尾处,可以看到Coconut Shrimp。我们成功了!
回想一下,访问令牌会在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请求,因为这能让黑客轻松地获取客户的信息。我将决定权交给你自己,你可以自行决定如何保护“/orders”端点和API的其他组成部分。
使用curl来管理Taco Cloud应用程序,对于熟悉和了解OAuth 2令牌如何允许我们访问资源,是一种很好的方式。但是,最终我们想要有一个真正的可以管理配料的客户端应用。接下来,我们将关注点转移到创建支持OAuth的客户端,以获取访问令牌并对API发送请求上。