Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth2 auto configuration support for Eureka Client. #2563

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions docs/src/main/asciidoc/spring-cloud-netflix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,35 @@ See {github-code}/spring-cloud-netflix-eureka-client/src/main/java/org/springfra

To disable the Eureka Discovery Client you can set `eureka.client.enabled` to `false`.

=== Authenticating with the Eureka Server
=== Basic authentication with the Eureka Server

HTTP basic authentication will be automatically added to your eureka
client if one of the `eureka.client.serviceUrl.defaultZone` URLs has
credentials embedded in it (curl style, like
`http://user:password@localhost:8761/eureka`). For more complex needs
you can create a `@Bean` of type `DiscoveryClientOptionalArgs` and
inject `ClientFilter` instances into it, all of which will be applied
`http://user:password@localhost:8761/eureka`).

=== OAuth2 client support

OAuth 2 support will be auto configured when `org.springframework.security.oauth:spring-security-oauth2`
is available in your classpath and you provide a `@Bean` of type `EurekaOAuth2ResourceDetails` within
your context. OAuth2 resource details can by configured with the following properties:

.application.yml
----
eureka:
client:
oauth2:
client_secret: client-secret
client_id: user
access_token_uri: oauth2-token-uri
----

IMPORTANT: OAuth2 client support is only supported with Rest Template. Jersey dependencies must be excluded. See <<EurekaClient without Jersey>> for more information.

=== Custom authentication

For more complex needs you can create a `@Bean` of type `DiscoveryClientOptionalArgs` and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention this is for jersey specifically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well not exactly true, if you use the rest template you can still extend the DiscoveryClientOptionalArgs (that's what we do on SCS)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's right

inject `ClientFilter` or `RestTemplace` instances into it, all of which will be applied
to the calls from the client to the server.

NOTE: Because of a limitation in Eureka it isn't possible to support
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-config.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-config.version>
<spring-cloud-stream.version>Elmhurst.BUILD-SNAPSHOT</spring-cloud-stream.version>
<spring-security-oauth2.version>2.2.1.RELEASE</spring-security-oauth2.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't managed by boot?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if there are plans to make this managed on boot. @rwinch any opinion on that? I don't need autoconfiguration, just the dependencies. We are talking about boot 2.0.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daniellavoie Thanks for reaching out! There are no plans for the old OAuth project version to be managed by Spring Boot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a chat with Rob, best option is to migrate OAuth2RestTemplate to WebClient using Spring Security 5. I'll reword that PR.

Copy link
Contributor Author

@daniellavoie daniellavoie Dec 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it won't be as easy. Spring Security 5 doesn't support automatic token retrieval for resource servers. The old OAuth2RestTemplate handled that for us. @rwinch plans on integrating that for Security 5.1. We have 3 options:

  • Wait for Security 5.1 - See Spring Security #4921
  • Integrate the old non-managed lib until migration to Security 5.1
  • Write token retrieval logic with WebClient (could be base work for Security 5.1).

I think the most reasonable option is to wait for Security 5.1

@ryanjbaxter @spencergibb Any opinion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont see any reason why we need this PR right now so I dont see why we cant wait.

<!-- Has to be a stable version (not one that depends on this version of netflix): -->
<donotreplacespring-cloud-contract.version>1.2.0.RELEASE</donotreplacespring-cloud-contract.version>

Expand Down Expand Up @@ -119,6 +120,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth2.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
Expand Down
6 changes: 6 additions & 0 deletions spring-cloud-netflix-eureka-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
<artifactId>ribbon-httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@

package org.springframework.cloud.netflix.eureka.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
import org.springframework.cloud.netflix.eureka.http.BasicEurekaRestTemplateFactory;
import org.springframework.cloud.netflix.eureka.http.EurekaRestTemplateFactory;
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactories;
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -31,12 +36,37 @@
* @author Daniel Lavoie
*/
@Configuration
@AutoConfigureAfter(EurekaOAuth2AutoConfiguration.class)
public class DiscoveryClientOptionalArgsConfiguration {

@Bean
@ConditionalOnMissingBean(value = EurekaRestTemplateFactory.class)
public EurekaRestTemplateFactory eurekaRestTemplateFactory() {
return new BasicEurekaRestTemplateFactory();
}

@Bean
@ConditionalOnMissingBean
public RestTemplateTransportClientFactory restTemplateTransportClientFactory(
EurekaRestTemplateFactory eurekaRestTemplateFactory) {
return new RestTemplateTransportClientFactory(eurekaRestTemplateFactory);
}

@Bean
@ConditionalOnMissingBean
public RestTemplateTransportClientFactories restTemplateTransportClientFactories(
RestTemplateTransportClientFactory restTemplateTransportClientFactory) {
return new RestTemplateTransportClientFactories(
restTemplateTransportClientFactory);
}

@Bean
@ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter")
@ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs() {
return new RestTemplateDiscoveryClientOptionalArgs();
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(
RestTemplateTransportClientFactories restTemplateTransportClientFactories) {
return new RestTemplateDiscoveryClientOptionalArgs(
restTemplateTransportClientFactories);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.springframework.cloud.netflix.eureka.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.eureka.http.EurekaRestTemplateFactory;
import org.springframework.cloud.netflix.eureka.http.oauth2.EurekaOAuth2ResourceDetails;
import org.springframework.cloud.netflix.eureka.http.oauth2.OAuth2EurekaRestTemplateFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;

@Configuration
@ConditionalOnClass(BaseOAuth2ProtectedResourceDetails.class)
public class EurekaOAuth2AutoConfiguration {
@Bean
@ConditionalOnProperty("eureka.client.oauth2.client-id")
public EurekaOAuth2ResourceDetails eurekaOAuth2ResourceDetails() {
return new EurekaOAuth2ResourceDetails();
}

@Bean
@ConditionalOnBean(EurekaOAuth2ResourceDetails.class)
public EurekaRestTemplateFactory eurekaRestTemplateFactory(
EurekaOAuth2ResourceDetails eurekaOAuth2ResourceDetails) {
return new OAuth2EurekaRestTemplateFactory(eurekaOAuth2ResourceDetails);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.netflix.eureka.http;

import java.net.URI;
import java.net.URISyntaxException;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.web.client.RestTemplate;

/**
* @author Daniel Lavoie
*/
public class BasicEurekaRestTemplateFactory implements EurekaRestTemplateFactory {
@Override
public RestTemplate newRestTemplate(String serviceUrl) {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
try {
URI serviceURI = new URI(serviceUrl);
if (serviceURI.getUserInfo() != null) {
String[] credentials = serviceURI.getUserInfo().split(":");
if (credentials.length == 2) {
restTemplateBuilder.interceptors(new BasicAuthorizationInterceptor(
credentials[0], credentials[1]));
}
}
}
catch (URISyntaxException ignore) {

}

return restTemplateBuilder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.netflix.eureka.http;

import org.springframework.web.client.RestTemplate;

/**
* @author Daniel Lavoie
*/
public interface EurekaRestTemplateFactory {
RestTemplate newRestTemplate(String serviceUrl);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;

/**
* Eureka client extension that allows customization of the transport client.
*
* @author Daniel Lavoie
*/
public class RestTemplateDiscoveryClientOptionalArgs
extends AbstractDiscoveryClientOptionalArgs<Void> {
public RestTemplateDiscoveryClientOptionalArgs() {
setTransportClientFactories(new RestTemplateTransportClientFactories());
public RestTemplateDiscoveryClientOptionalArgs(
RestTemplateTransportClientFactories restTemplateTransportClientFactories) {
setTransportClientFactories(restTemplateTransportClientFactories);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import com.netflix.discovery.util.StringUtil;

/**
* {@link RestTemplate} based implementation of an {@link EurekaHttpClient}.
*
* @author Daniel Lavoie
*/
public class RestTemplateEurekaHttpClient implements EurekaHttpClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
*/
public class RestTemplateTransportClientFactories
implements TransportClientFactories<Void> {
private final RestTemplateTransportClientFactory restTemplateTransportClientFactory;

public RestTemplateTransportClientFactories(
RestTemplateTransportClientFactory restTemplateTransportClientFactory) {
this.restTemplateTransportClientFactory = restTemplateTransportClientFactory;
}

@Override
public TransportClientFactory newTransportClientFactory(
Expand All @@ -44,15 +50,15 @@ public TransportClientFactory newTransportClientFactory(
public TransportClientFactory newTransportClientFactory(
EurekaClientConfig clientConfig, Collection<Void> additionalFilters,
InstanceInfo myInstanceInfo) {
return new RestTemplateTransportClientFactory();
return restTemplateTransportClientFactory;
}

@Override
public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
final Collection<Void> additionalFilters,
final InstanceInfo myInstanceInfo,
final Optional<SSLContext> sslContext,
final Optional<HostnameVerifier> hostnameVerifier) {
return new RestTemplateTransportClientFactory();
public TransportClientFactory newTransportClientFactory(
final EurekaClientConfig clientConfig,
final Collection<Void> additionalFilters, final InstanceInfo myInstanceInfo,
final Optional<SSLContext> sslContext,
final Optional<HostnameVerifier> hostnameVerifier) {
return restTemplateTransportClientFactory;
}
}
Loading