Skip to content

Add OAuth2-Client ConnectionDetails and Keycloak Docker-Compose support #39391

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

Closed
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Copyright 2012-2024 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
*
* https://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.boot.autoconfigure.security.oauth2.client;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;

/**
* Details required to establish connections to a OAuth2 authentication server.
*
* @author Philipp Kessler
* @since 3.3.0
*/
public interface OAuth2ClientConnectionDetails extends ConnectionDetails {

default Map<String, Registration> getRegistrations() {
return new HashMap<>();
}

default Map<String, Provider> getProviders() {
return new HashMap<>();
}

interface Registration {

/**
* Reference to the OAuth 2.0 provider to use. May reference an element from the
* 'provider' property or used one of the commonly used providers (google, github,
* facebook, okta).
* @return reference to the OAuth 2.0 provider to use.
*/
String getProvider();

/**
* Client ID for the registration.
* @return client ID for the registration.
*/
String getClientId();

/**
* Client secret of the registration.
* @return client secret of the registration.
*/
String getClientSecret();

/**
* Client authentication method. May be left blank when using a pre-defined
* provider.
* @return client authentication method.
*/
String getClientAuthenticationMethod();

/**
* Authorization grant type. May be left blank when using a pre-defined provider.
* @return authorization grant type.
*/
String getAuthorizationGrantType();

/**
* Redirect URI. May be left blank when using a pre-defined provider.
* @return redirect URI. May be left blank when using a pre-defined provider.
*/
String getRedirectUri();

/**
* Authorization scopes. When left blank the provider's default scopes, if any,
* will be used.
* @return authorization scopes.
*/
Set<String> getScopes();

/**
* Client name. May be left blank when using a pre-defined provider.
* @return client name. May be left blank when using a pre-defined provider.
*/
String getClientName();

static Registration of(String provider, String clientId, String clientSecret, String clientAuthenticationMethod,
String authorizationGrantType, String redirectUri, Set<String> scope, String clientName) {
return new Registration() {
@Override
public String getProvider() {
return provider;
}

@Override
public String getClientId() {
return clientId;
}

@Override
public String getClientSecret() {
return clientSecret;
}

@Override
public String getClientAuthenticationMethod() {
return clientAuthenticationMethod;
}

@Override
public String getAuthorizationGrantType() {
return authorizationGrantType;
}

@Override
public String getRedirectUri() {
return redirectUri;
}

@Override
public Set<String> getScopes() {
return scope;
}

@Override
public String getClientName() {
return clientName;
}
};
}

}

interface Provider {

/**
* Authorization URI for the provider.
* @return authorization URI for the provider.
*/
default String getAuthorizationUri() {
return null;
}

/**
* Token URI for the provider.
* @return token URI for the provider.
*/
default String getTokenUri() {
return null;
}

/**
* User info URI for the provider.
* @return user info URI for the provider.
*/
default String getUserInfoUri() {
return null;
}

/**
* User info authentication method for the provider.
* @return user info authentication method for the provider.
*/
default String getUserInfoAuthenticationMethod() {
return null;
}

/**
* Name of the attribute that will be used to extract the username from the call
* to 'userInfoUri'.
* @return name of the attribute that will be used to extract the username from
* the call * to 'userInfoUri'
*/
default String getUserNameAttribute() {
return null;
}

/**
* JWK set URI for the provider.
* @return jwk set URI for the provider.
*/
default String getJwkSetUri() {
return null;
}

/**
* URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
* Authorization Server Metadata endpoint defined by RFC 8414.
* @return uri that can either be an OpenID Connect discovery endpoint or an OAuth
* 2.0 * Authorization Server Metadata endpoint defined by RFC 8414.
*/
default String getIssuerUri() {
return null;
}

static Provider of(String authorizationUri, String tokenUri, String userInfoUri,
String userInfoAuthenticationMethod, String userNameAttributes, String jwkSetUri, String issuerUri) {
return new Provider() {
@Override
public String getAuthorizationUri() {
return authorizationUri;
}

@Override
public String getTokenUri() {
return tokenUri;
}

@Override
public String getUserInfoUri() {
return userInfoUri;
}

@Override
public String getUserInfoAuthenticationMethod() {
return userInfoAuthenticationMethod;
}

@Override
public String getUserNameAttribute() {
return userNameAttributes;
}

@Override
public String getJwkSetUri() {
return jwkSetUri;
}

@Override
public String getIssuerUri() {
return issuerUri;
}
};
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientConnectionDetails.Provider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientConnectionDetails.Registration;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionException;
Expand All @@ -33,7 +34,8 @@
import org.springframework.util.StringUtils;

/**
* Maps {@link OAuth2ClientProperties} to {@link ClientRegistration ClientRegistrations}.
* Maps {@link OAuth2ClientConnectionDetails} to {@link ClientRegistration
* ClientRegistrations}.
*
* @author Phillip Webb
* @author Thiago Hirata
Expand All @@ -42,16 +44,16 @@
* @author Andy Wilkinson
* @since 3.1.0
*/
public final class OAuth2ClientPropertiesMapper {
public final class OAuth2ClientConnectionDetailsMapper {

private final OAuth2ClientProperties properties;
private final OAuth2ClientConnectionDetails connectionDetails;

/**
* Creates a new mapper for the given {@code properties}.
* @param properties the properties to map
* @param connectionDetails the properties to map
*/
public OAuth2ClientPropertiesMapper(OAuth2ClientProperties properties) {
this.properties = properties;
public OAuth2ClientConnectionDetailsMapper(OAuth2ClientConnectionDetails connectionDetails) {
this.connectionDetails = connectionDetails;
}

/**
Expand All @@ -60,14 +62,14 @@ public OAuth2ClientPropertiesMapper(OAuth2ClientProperties properties) {
*/
public Map<String, ClientRegistration> asClientRegistrations() {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
this.properties.getRegistration()
this.connectionDetails.getRegistrations()
.forEach((key, value) -> clientRegistrations.put(key,
getClientRegistration(key, value, this.properties.getProvider())));
getClientRegistration(key, value, this.connectionDetails.getProviders())));
return clientRegistrations;
}

private static ClientRegistration getClientRegistration(String registrationId,
OAuth2ClientProperties.Registration properties, Map<String, Provider> providers) {
private static ClientRegistration getClientRegistration(String registrationId, Registration properties,
Map<String, Provider> providers) {
Builder builder = getBuilderFromIssuerIfPossible(registrationId, properties.getProvider(), providers);
if (builder == null) {
builder = getBuilder(registrationId, properties.getProvider(), providers);
Expand All @@ -82,7 +84,7 @@ private static ClientRegistration getClientRegistration(String registrationId,
.as(AuthorizationGrantType::new)
.to(builder::authorizationGrantType);
map.from(properties::getRedirectUri).to(builder::redirectUri);
map.from(properties::getScope).as(StringUtils::toStringArray).to(builder::scope);
map.from(properties::getScopes).as(StringUtils::toStringArray).to(builder::scope);
map.from(properties::getClientName).to(builder::clientName);
return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2012-2023 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
*
* https://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.boot.autoconfigure.security.oauth2.client;

import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
* Adapts {@link OAuth2ClientProperties} to {@link OAuth2ClientConnectionDetails}.
*
* @author Philipp Kessler
* @since 3.3.0
*/
public class PropertiesOAuth2ClientConnectionDetails implements OAuth2ClientConnectionDetails {

private final OAuth2ClientProperties properties;

public PropertiesOAuth2ClientConnectionDetails(OAuth2ClientProperties properties) {
this.properties = properties;
}

@Override
public Map<String, Registration> getRegistrations() {
return this.properties.getRegistration()
.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, (entry) -> {
OAuth2ClientProperties.Registration registration = entry.getValue();
return Registration.of(registration.getProvider(), registration.getClientId(),
registration.getClientSecret(), registration.getClientAuthenticationMethod(),
registration.getAuthorizationGrantType(), registration.getRedirectUri(),
registration.getScope(), registration.getClientName());
}));
}

@Override
public Map<String, Provider> getProviders() {
return this.properties.getProvider().entrySet().stream().collect(Collectors.toMap(Entry::getKey, (entry) -> {
OAuth2ClientProperties.Provider provider = entry.getValue();
return Provider.of(provider.getAuthorizationUri(), provider.getTokenUri(), provider.getUserInfoUri(),
provider.getUserInfoAuthenticationMethod(), provider.getUserNameAttribute(),
provider.getJwkSetUri(), provider.getIssuerUri());
}));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
Expand All @@ -39,7 +37,6 @@
* @since 2.1.0
*/
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class })
@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
Expand Down
Loading