2

I really would not put it here but I am really confused, I want to achieve the following.

I am running

  • Java 14
  • Spring Cloud Gateway version: Hoxton.SR3
  • Spring Boot version: 2.2.5.RELEASE

Now I want to integrate security to my Gateway and to all the downstream microservices. Eventually, I decided to go with Firebase as an Identity Provider (IDP). My Angular application will get JWT token from Firebase and send it in every request to Cloud Gateway. So, the Gateway will start to act as a resource server ONLY and that is it.

Here how I tried to give it a go. Set up and Spring Cloud Gateway to act like Resource Server at the same time. Quite well explained here Spring Security Docs.

Here what my configuration looks like

@EnableWebFluxSecurity
public class ResourceServerSecurityConfiguration {

  @Bean
  public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    // @formatter:off
    http
        .authorizeExchange()
        .anyExchange().authenticated()
        .and()
        .oauth2ResourceServer()
        .jwt();
    return http.build();
    // @formatter:on
  }
}

And 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}

As you see in this YAML I provided the jwk-set-uri and issuer to validate incoming tokens.

At this point, all work quite as accepted. All the requests have to have valid JWT in the Authentication header.

Next,

I want my gateway to use WebClient and call several services to aggregate data for the frontend.

Here how I am trying to configure my client.

  @Bean
  @LoadBalanced
  public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder()
        .filter(new ServletBearerExchangeFilterFunction());
  }

As you see It uses ServletBearerExchangeFilterFunction this is where my real problem comes in.

I already checked that when Spring configuring oauth2ResourceServer it uses NoOpServerSecurityContextRepository. From what I understand so far that this is exactly a repository that used to register context per request. Also, I understand that it makes sense to use NoOp as we want to be stateless. However what I do not understand how to make ServletBearerExchangeFilterFunction to work properly and pass downstream my tokens.

I spend now quite a lot of time trying to figure out the correct way of doing this.

Found this: Spring Boot 2 OIDC (OAuth2) client / resource server not propagating the access token in the WebClient

Github: https://github.com/spring-projects/spring-security/issues/7771

And even according to this what I try to do should be legit and possible. Not sure where I am mistaken.

VostanAzatyan
  • 618
  • 1
  • 9
  • 22
  • Hi Vostan, could you provide a sample so that I can give it a try? – codependent Apr 29 '20 at 21:33
  • @codependent Thanks for fast responding, https://github.com/Vostan/spring-cloud-gatway-resource-server Here I put some example in quite a fast manner :) sorry if something is off However there is one small issue, I guess you have to set up some IDP to get some token. – VostanAzatyan Apr 29 '20 at 22:25
  • @codependent I wonder if you did manage to have a look ? – VostanAzatyan May 07 '20 at 08:05

2 Answers2

2

I figured this, the thing is that ReactiveSecurityContext is available only when you are within the reactive flow and ServletBearerExchangeFilterFunction meant to be for Servlet calls.

Eventually, I just wrote my own filter function which listens to ReactiveSecurity context and sets the Authorization header. Here it is.

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();
  }


}
VostanAzatyan
  • 618
  • 1
  • 9
  • 22
1

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.

If you set up SCG(Spring Cloud Gateway) as oauth2 resource server you must do more custom,Maybe like this. I think you should not do that.

You can use gateway use as route,and send access token header to SCG,and SCG will take access token to oauth2 resource server,and check permission on oauth2 resource server side.

Spring Cloud Gateway Token Relay GatewayFilter Factory say:

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.

So that can keep oauth2 clearness in you app.

  • Thanks for responding, but actually the thing here is that `ServletBearerExchangeFilterFunction` is Servlet filter function. In reality, I needed to write some sort of custom FIlter which could get a token from ReactiveSecurityContext and set it as a header. See my answer – VostanAzatyan Mar 11 '21 at 16:44