spring-securityoauthspring-cloudspring-cloud-netflixspring-cloud-gateway

How to Pass access token on Spring Cloud Gateway


I'm new on OAuth2 and Spring Cloud Gateway(And WebFlux things).

My team decided to move from Zuul gateway to Spring Cloud Gateway. And current Spring Cloud version is "Greenwich.SR1"

The problem is spring cloud gateway always response 401.

How to pass access token on Spring Cloud Gateway properly?

Auth server :

@EnableEurekaClient
@EnableAuthorizationServer
@SpringBootApplication
public class AuthServer {...} // jwtAccessTokenConverter bean included

Zuul server is :

@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulServer {...}

Zuul server properties :

zuul:
  sensitive-headers: Cookie,Set-Cookie
  ignored-services: '*'
  routes:
    auth: /auth/**

Spring Cloud Gateway Server properties :

spring:
  cloud:
    gateway:
      routes:
        - id: auth
          uri: lb://auth
          predicates:
            - Method=POST
            - Path=/auth/**
          filters:
            - RemoveRequestHeader= Cookie,Set-Cookie
            - StripPrefix=1

Spring Cloud server build.gradle :

plugins {
    id 'java'
    id "io.freefair.lombok" version "3.2.0"
    id "org.springframework.boot" version "2.1.5.RELEASE"
    id "io.spring.dependency-management" version "1.0.6.RELEASE"
}

version = '1.0.0-SNAPSHOT'
description = 'edge-service2'
sourceCompatibility = '11'

dependencies {
    implementation platform("org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion")
    implementation "org.springframework.boot:spring-boot-starter-security"
    implementation "org.springframework.cloud:spring-cloud-starter-netflix-eureka-client"
    implementation "org.springframework.cloud:spring-cloud-starter-netflix-ribbon"
    implementation "org.springframework.cloud:spring-cloud-starter-netflix-hystrix"

    implementation('org.springframework.cloud:spring-cloud-starter-gateway')

    implementation "org.springframework.cloud:spring-cloud-config-client"
    implementation "de.codecentric:spring-boot-admin-starter-client:$springBootAdminVersion"
    implementation "net.gpedro.integrations.slack:slack-webhook:1.4.0"
    testImplementation "org.springframework.boot:spring-boot-starter-test"
}


springBoot {
    buildInfo()
}

bootJar {
    archiveName "${project.name}.jar"
}

Solution

  • You can create a custom configuration class and annotate it with @Configuration.

    Step 1: Get the instance of ServerOAuth2AuthorizedClientRepository Interface

    @Autowired
    private ServerOAuth2AuthorizedClientRepository clientRegistrationRepository;
    

    Step 2: Apply Global filter and fetch the token from the OAuth2AuthorizedClient

    // For Getting token from request
    SecurityContextImpl context =
        exchange.getSession().toProcessor().block().getAttribute("SPRING_SECURITY_CONTEXT");
    
    OAuth2AuthenticationToken oauthToken =
        (OAuth2AuthenticationToken) context.getAuthentication();
    
    Mono<OAuth2AuthorizedClient> authorizedClient = clientRegistrationRepository
        .loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(),
            context.getAuthentication(), exchange);
    
    OAuth2AuthorizedClient client = authorizedClient.toProcessor().block();
    
    String accessToken = client.getAccessToken().getTokenValue();
    
    LOG.info("Access Token value: {}", accessToken);
    

    Here is the complete configuration class which does the job perfectly.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.context.SecurityContextImpl;
    import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
    import reactor.core.publisher.Mono;
    
    @Configuration
    public class GatewayConfig {
    
      private static final Logger LOG = LoggerFactory.getLogger(GatewayConfig.class);
    
      @Autowired
      private ServerOAuth2AuthorizedClientRepository clientRegistrationRepository;
    
      @SuppressWarnings("deprecation")
      @Bean
      public GlobalFilter customGlobalFilter() {
        return (exchange, chain) -> exchange.getPrincipal().map(principal -> {
          if (principal instanceof OAuth2AuthenticationToken) {
            // For Getting token from request
            SecurityContextImpl context =
                exchange.getSession().toProcessor().block().getAttribute("SPRING_SECURITY_CONTEXT");
    
            OAuth2AuthenticationToken oauthToken =
                (OAuth2AuthenticationToken) context.getAuthentication();
    
            Mono<OAuth2AuthorizedClient> authorizedClient = clientRegistrationRepository
                .loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(),
                    context.getAuthentication(), exchange);
    
            OAuth2AuthorizedClient client = authorizedClient.toProcessor().block();
    
            String accessToken = client.getAccessToken().getTokenValue();
    
            LOG.info("Access Token value: {}", accessToken);
          }
          return exchange;
        }).flatMap(chain::filter).then(Mono.fromRunnable(() -> {
        }));
      }
    }