Reputation: 621
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
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
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:
[1] https://auth0.com/blog/id-token-access-token-what-is-the-difference/
Upvotes: 2