Spring云网关+Spring安全资源服务器
Spring Cloud Gateway + Spring security resource server
我真的不会把它放在这里,但我真的很困惑,
我想实现以下目标。
我是运行
- Java 14
Spring Cloud Gateway
版本:Hoxton.SR3
Spring Boot
版本:2.2.5.RELEASE
现在我想将安全性集成到我的网关和所有下游微服务中。最终,我决定使用 Firebase 作为身份提供者 (IDP)。我的 Angular 应用程序将从 Firebase 获取 JWT 令牌,并在每个请求中将其发送到 Cloud Gateway。因此,网关将开始仅充当资源服务器,仅此而已。
这是我尝试尝试的方式。
设置和Spring Cloud Gateway
同时充当Resource Server。
这里解释得很好Spring Security Docs。
这是我的配置
@EnableWebFluxSecurity
public class ResourceServerSecurityConfiguration {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
// @formatter:on
}
}
和application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
issuer-uri: https://securetoken.google.com/{$app.name}
如您在此 YAML 中所见,我提供了 jwk-set-uri 和发行者来验证传入的令牌。
至此,所有的工作都可以接受了。所有请求都必须在身份验证中具有有效的 JWT header。
接下来,
我希望我的网关使用 WebClient
并调用多个服务来为前端聚合数据。
这是我尝试配置客户端的方式。
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction());
}
如您所见,它使用 ServletBearerExchangeFilterFunction
这就是我真正的问题所在。
我已经检查过 Spring 配置 oauth2ResourceServer 时它使用 NoOpServerSecurityContextRepository
。到目前为止,据我了解,这正是一个用于为每个请求注册上下文的存储库。另外,我知道使用 NoOp 是有意义的,因为我们想要无状态。但是我不明白如何使 ServletBearerExchangeFilterFunction
正常工作并向下游传递我的令牌。
我现在花了很多时间试图找出正确的方法。
找到这个:
Github: https://github.com/spring-projects/spring-security/issues/7771
即使根据这个,我尝试做的事情也应该是合法且可能的。不知道我错在哪里。
Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
如果您将 SCG(Spring Cloud Gateway) 设置为 oauth2 资源服务器,您必须做更多的自定义,也许像 this。我认为你不应该那样做。
你可以使用网关作为路由,将access token header发送给SCG,SCG将access token拿到oauth2资源服务器,并在oauth2资源服务器端检查权限。
Spring Cloud Gateway Token Relay GatewayFilter Factory 说:
The {githubmaster}/src/main/java/org/springframework/cloud/gateway/security/TokenRelayGatewayFilterFactory.java[filter] extracts an access token from the currently authenticated user, and puts it in a request header for the downstream requests.
这样可以在您的应用程序中保持 oauth2 的清晰度。
我想通了,问题是 ReactiveSecurityContext 仅在您处于反应流中时可用,而 ServletBearerExchangeFilterFunction 用于 Servlet 调用。
最终,我编写了自己的过滤器函数,它监听 ReactiveSecurity 上下文并设置授权 header。在这里。
public class BearerExchangeFilterFunction implements ExchangeFilterFunction {
@Override
@NonNull
public Mono<ClientResponse> filter(@NonNull ClientRequest request, ExchangeFunction next) {
return ReactiveSecurityContextHolder.getContext()
.map(c -> (c.getAuthentication().getCredentials()))
.cast(AbstractOAuth2Token.class)
.checkpoint()
.map(token -> bearer(request, token))
.defaultIfEmpty(request)
.flatMap(next::exchange);
}
private ClientRequest bearer(ClientRequest request, AbstractOAuth2Token token) {
return ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(token.getTokenValue()))
.build();
}
}
我真的不会把它放在这里,但我真的很困惑, 我想实现以下目标。
我是运行
- Java 14
Spring Cloud Gateway
版本:Hoxton.SR3
Spring Boot
版本:2.2.5.RELEASE
现在我想将安全性集成到我的网关和所有下游微服务中。最终,我决定使用 Firebase 作为身份提供者 (IDP)。我的 Angular 应用程序将从 Firebase 获取 JWT 令牌,并在每个请求中将其发送到 Cloud Gateway。因此,网关将开始仅充当资源服务器,仅此而已。
这是我尝试尝试的方式。
设置和Spring Cloud Gateway
同时充当Resource Server。
这里解释得很好Spring Security Docs。
这是我的配置
@EnableWebFluxSecurity
public class ResourceServerSecurityConfiguration {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
// @formatter:on
}
}
和application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
issuer-uri: https://securetoken.google.com/{$app.name}
如您在此 YAML 中所见,我提供了 jwk-set-uri 和发行者来验证传入的令牌。
至此,所有的工作都可以接受了。所有请求都必须在身份验证中具有有效的 JWT header。
接下来,
我希望我的网关使用 WebClient
并调用多个服务来为前端聚合数据。
这是我尝试配置客户端的方式。
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction());
}
如您所见,它使用 ServletBearerExchangeFilterFunction
这就是我真正的问题所在。
我已经检查过 Spring 配置 oauth2ResourceServer 时它使用 NoOpServerSecurityContextRepository
。到目前为止,据我了解,这正是一个用于为每个请求注册上下文的存储库。另外,我知道使用 NoOp 是有意义的,因为我们想要无状态。但是我不明白如何使 ServletBearerExchangeFilterFunction
正常工作并向下游传递我的令牌。
我现在花了很多时间试图找出正确的方法。
找到这个:
Github: https://github.com/spring-projects/spring-security/issues/7771
即使根据这个,我尝试做的事情也应该是合法且可能的。不知道我错在哪里。
Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
如果您将 SCG(Spring Cloud Gateway) 设置为 oauth2 资源服务器,您必须做更多的自定义,也许像 this。我认为你不应该那样做。
你可以使用网关作为路由,将access token header发送给SCG,SCG将access token拿到oauth2资源服务器,并在oauth2资源服务器端检查权限。
Spring Cloud Gateway Token Relay GatewayFilter Factory 说:
The {githubmaster}/src/main/java/org/springframework/cloud/gateway/security/TokenRelayGatewayFilterFactory.java[filter] extracts an access token from the currently authenticated user, and puts it in a request header for the downstream requests.
这样可以在您的应用程序中保持 oauth2 的清晰度。
我想通了,问题是 ReactiveSecurityContext 仅在您处于反应流中时可用,而 ServletBearerExchangeFilterFunction 用于 Servlet 调用。
最终,我编写了自己的过滤器函数,它监听 ReactiveSecurity 上下文并设置授权 header。在这里。
public class BearerExchangeFilterFunction implements ExchangeFilterFunction {
@Override
@NonNull
public Mono<ClientResponse> filter(@NonNull ClientRequest request, ExchangeFunction next) {
return ReactiveSecurityContextHolder.getContext()
.map(c -> (c.getAuthentication().getCredentials()))
.cast(AbstractOAuth2Token.class)
.checkpoint()
.map(token -> bearer(request, token))
.defaultIfEmpty(request)
.flatMap(next::exchange);
}
private ClientRequest bearer(ClientRequest request, AbstractOAuth2Token token) {
return ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(token.getTokenValue()))
.build();
}
}