lv0gun9
lv0gun9

Reputation: 621

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

Upvotes: 1

Views: 6023

Answers (2)

Vijay
Vijay

Reputation: 663

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

Upvotes: 1

NoNote24
NoNote24

Reputation: 86

There is a feature in Spring Cloud Security for relaying the access token to downstream services via Spring Cloud Gateway: https://cloud.spring.io/spring-cloud-static/spring-cloud-security/2.1.3.RELEASE/single/spring-cloud-security.html#_token_relay

Simply use the TokenRelay Filter for your route or default configuration. However, this forwards just the access token. "The access token is the artifact that allows the client application to access the user's resource"[1], whereas "an ID token is an artifact that proves that the user has been authenticated"[1], and it also contains the user attributes.

It seems that's what you want anyway, but for all the people out there that use OIDC and want to relay the ID Token, here's some more information. Write a custom GatewayFilterFactory where you:

  • get the authenticated Principal via exchange.getPrincipal().ofType(OAuth2AuthenticationToken.class)
  • map it until you get an oAuth2User Objekt
  • cast it to an OidcUser
  • now you can do oidcUser.getIdToken.getTokenValue()
  • put it into a header of your choice and thus you can also forward the ID Token, not only the Access Token.

[1] https://auth0.com/blog/id-token-access-token-what-is-the-difference/

Upvotes: 2

Related Questions