Skip to content

Commit 96be5f4

Browse files
committed
Migrates token relay from spring-cloud-security.
Adds refresh token support. Fixes spring-attic/spring-cloud-security#175 Fixes gh-1975 See spring-attic/spring-cloud-security#231
1 parent b12a5f2 commit 96be5f4

File tree

7 files changed

+419
-0
lines changed

7 files changed

+419
-0
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<junit-pioneer.version>0.9.0</junit-pioneer.version>
5757
<spring-cloud-circuitbreaker.version>2.0.0-SNAPSHOT</spring-cloud-circuitbreaker.version>
5858
<spring-cloud-commons.version>3.0.0-SNAPSHOT</spring-cloud-commons.version>
59+
<spring-security-oauth2-autoconfigure.version>2.3.4.RELEASE</spring-security-oauth2-autoconfigure.version>
5960
<testcontainers.version>1.14.3</testcontainers.version>
6061
</properties>
6162

@@ -98,6 +99,11 @@
9899
<artifactId>spring-boot-devtools</artifactId>
99100
<version>${spring-boot.version}</version>
100101
</dependency>
102+
<dependency>
103+
<groupId>org.springframework.security.oauth.boot</groupId>
104+
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
105+
<version>${spring-security-oauth2-autoconfigure.version}</version>
106+
</dependency>
101107
<dependency>
102108
<groupId>io.projectreactor.tools</groupId>
103109
<artifactId>blockhound-junit-platform</artifactId>
@@ -123,6 +129,7 @@
123129
<module>spring-cloud-gateway-mvc</module>
124130
<module>spring-cloud-gateway-webflux</module>
125131
<module>spring-cloud-gateway-server</module>
132+
<module>spring-cloud-gateway-server-security</module>
126133
<module>spring-cloud-starter-gateway</module>
127134
<module>spring-cloud-gateway-sample</module>
128135
<module>docs</module>

spring-cloud-gateway-dependencies/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
<artifactId>spring-cloud-gateway-server</artifactId>
3838
<version>${project.version}</version>
3939
</dependency>
40+
<dependency>
41+
<groupId>org.springframework.cloud</groupId>
42+
<artifactId>spring-cloud-gateway-server-security</artifactId>
43+
<version>${project.version}</version>
44+
</dependency>
4045
<dependency>
4146
<groupId>org.springframework.cloud</groupId>
4247
<artifactId>spring-cloud-starter-gateway</artifactId>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.springframework.cloud</groupId>
9+
<artifactId>spring-cloud-gateway</artifactId>
10+
<version>3.0.0-SNAPSHOT</version>
11+
<relativePath>..</relativePath> <!-- lookup parent from repository -->
12+
</parent>
13+
<artifactId>spring-cloud-gateway-server-security</artifactId>
14+
<packaging>jar</packaging>
15+
<name>Spring Cloud Gateway Server Security</name>
16+
<description>Spring Cloud Gateway Server Security</description>
17+
<properties>
18+
<main.basedir>${basedir}/..</main.basedir>
19+
</properties>
20+
21+
<dependencies>
22+
<dependency>
23+
<groupId>org.springframework.cloud</groupId>
24+
<artifactId>spring-cloud-gateway-server</artifactId>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-starter-oauth2-client</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-configuration-processor</artifactId>
33+
<optional>true</optional>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.springframework.boot</groupId>
37+
<artifactId>spring-boot-devtools</artifactId>
38+
<optional>true</optional>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.springframework.boot</groupId>
42+
<artifactId>spring-boot-autoconfigure-processor</artifactId>
43+
<optional>true</optional>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.springframework.boot</groupId>
47+
<artifactId>spring-boot-starter-test</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-starter-webflux</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.junit.vintage</groupId>
57+
<artifactId>junit-vintage-engine</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.junit-pioneer</groupId>
62+
<artifactId>junit-pioneer</artifactId>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.springframework.cloud</groupId>
67+
<artifactId>spring-cloud-test-support</artifactId>
68+
<scope>test</scope>
69+
</dependency>
70+
<dependency>
71+
<groupId>io.projectreactor</groupId>
72+
<artifactId>reactor-test</artifactId>
73+
<scope>test</scope>
74+
</dependency>
75+
<dependency>
76+
<groupId>org.assertj</groupId>
77+
<artifactId>assertj-core</artifactId>
78+
<scope>test</scope>
79+
</dependency>
80+
</dependencies>
81+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2013-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.security;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
23+
import org.springframework.boot.autoconfigure.security.SecurityProperties;
24+
import org.springframework.cloud.gateway.filter.GatewayFilter;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
28+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
29+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
30+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
31+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
32+
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
33+
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
34+
import org.springframework.security.web.server.SecurityWebFilterChain;
35+
36+
/**
37+
* @author Dave Syer
38+
*
39+
*/
40+
@Configuration(proxyBeanMethods = false)
41+
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
42+
@ConditionalOnClass({ GatewayFilter.class, OAuth2AuthorizedClient.class, SecurityWebFilterChain.class,
43+
SecurityProperties.class })
44+
@ConditionalOnWebApplication(type = Type.REACTIVE)
45+
public class TokenRelayAutoConfiguration {
46+
47+
@Bean
48+
public TokenRelayGatewayFilterFactory tokenRelayGatewayFilterFactory(
49+
ReactiveOAuth2AuthorizedClientManager clientManager) {
50+
return new TokenRelayGatewayFilterFactory(clientManager);
51+
}
52+
53+
@Bean
54+
public ReactiveOAuth2AuthorizedClientManager gatewayReactiveOAuth2AuthorizedClientManager(
55+
ReactiveClientRegistrationRepository clientRegistrationRepository,
56+
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
57+
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
58+
.builder().authorizationCode().refreshToken().build();
59+
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
60+
clientRegistrationRepository, authorizedClientRepository);
61+
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
62+
return authorizedClientManager;
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.security;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
import org.springframework.cloud.gateway.filter.GatewayFilter;
22+
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
23+
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
24+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
25+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
26+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
27+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
28+
import org.springframework.stereotype.Component;
29+
import org.springframework.web.server.ServerWebExchange;
30+
31+
/**
32+
* @author Joe Grandja
33+
*/
34+
@Component
35+
public class TokenRelayGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
36+
37+
private final ReactiveOAuth2AuthorizedClientManager clientManager;
38+
39+
public TokenRelayGatewayFilterFactory(ReactiveOAuth2AuthorizedClientManager clientManager) {
40+
super(Object.class);
41+
this.clientManager = clientManager;
42+
}
43+
44+
public GatewayFilter apply() {
45+
return apply((Object) null);
46+
}
47+
48+
@Override
49+
public GatewayFilter apply(Object config) {
50+
return (exchange, chain) -> exchange.getPrincipal()
51+
// .log("token-relay-filter")
52+
.filter(principal -> principal instanceof OAuth2AuthenticationToken)
53+
.cast(OAuth2AuthenticationToken.class)
54+
.flatMap(authentication -> authorizedClient(exchange, authentication))
55+
.map(OAuth2AuthorizedClient::getAccessToken).map(token -> withBearerAuth(exchange, token))
56+
// TODO: adjustable behavior if empty
57+
.defaultIfEmpty(exchange).flatMap(chain::filter);
58+
}
59+
60+
private Mono<OAuth2AuthorizedClient> authorizedClient(ServerWebExchange exchange,
61+
OAuth2AuthenticationToken oauth2Authentication) {
62+
String clientRegistrationId = oauth2Authentication.getAuthorizedClientRegistrationId();
63+
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
64+
.principal(oauth2Authentication).build();
65+
// TODO: use Mono.defer() for request above?
66+
return clientManager.authorize(request);
67+
}
68+
69+
private ServerWebExchange withBearerAuth(ServerWebExchange exchange, OAuth2AccessToken accessToken) {
70+
return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(accessToken.getTokenValue())))
71+
.build();
72+
}
73+
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2014-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.security;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
23+
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
24+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* @author Spencer Gibb
32+
*
33+
*/
34+
public class TokenRelayAutoConfigurationTests {
35+
36+
@Test
37+
public void beansAreCreated() {
38+
new ReactiveWebApplicationContextRunner()
39+
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class,
40+
ReactiveOAuth2ClientAutoConfiguration.class, TokenRelayAutoConfiguration.class))
41+
.withPropertyValues(
42+
"spring.security.oauth2.client.provider[testprovider].authorization-uri=http://localhost",
43+
"spring.security.oauth2.client.provider[testprovider].token-uri=http://localhost/token",
44+
"spring.security.oauth2.client.registration[test].provider=testprovider",
45+
"spring.security.oauth2.client.registration[test].authorization-grant-type=authorization_code",
46+
"spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect",
47+
"spring.security.oauth2.client.registration[test].client-id=login-client")
48+
.withUserConfiguration(TestConfig.class).withPropertyValues("debug=true").run(context -> {
49+
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class);
50+
assertThat(context).hasSingleBean(TokenRelayGatewayFilterFactory.class);
51+
});
52+
}
53+
54+
@Configuration
55+
protected static class TestConfig {
56+
57+
}
58+
59+
}

0 commit comments

Comments
 (0)