Merge pull request #34003 from sjohnr

* pr/34003:
  Polish "Add Spring Authorization Server support"
  Add Spring Authorization Server support

Closes gh-34003
pull/34729/head
Madhura Bhave 2 years ago
commit ff530e7210

@ -180,6 +180,7 @@ dependencies {
exclude group: "javax.xml.bind", module: "jaxb-api"
}
optional("org.springframework.security:spring-security-messaging")
optional("org.springframework.security:spring-security-oauth2-authorization-server")
optional("org.springframework.security:spring-security-oauth2-client")
optional("org.springframework.security:spring-security-oauth2-jose")
optional("org.springframework.security:spring-security-oauth2-resource-server")

@ -0,0 +1,499 @@
/*
* 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.server;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* OAuth 2.0 Authorization Server properties.
*
* @author Steve Riesenberg
* @since 3.1.0
*/
@ConfigurationProperties(prefix = "spring.security.oauth2.authorizationserver")
public class OAuth2AuthorizationServerProperties implements InitializingBean {
/**
* URL of the Authorization Server's Issuer Identifier.
*/
private String issuer;
/**
* Registered clients of the Authorization Server.
*/
private final Map<String, Client> client = new HashMap<>();
/**
* Authorization Server endpoints.
*/
private final Endpoint endpoint = new Endpoint();
public String getIssuer() {
return this.issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public Map<String, Client> getClient() {
return this.client;
}
public Endpoint getEndpoint() {
return this.endpoint;
}
@Override
public void afterPropertiesSet() {
validate();
}
public void validate() {
getClient().values().forEach(this::validateClient);
}
private void validateClient(Client client) {
if (!StringUtils.hasText(client.getRegistration().getClientId())) {
throw new IllegalStateException("Client id must not be empty.");
}
if (CollectionUtils.isEmpty(client.getRegistration().getClientAuthenticationMethods())) {
throw new IllegalStateException("Client authentication methods must not be empty.");
}
if (CollectionUtils.isEmpty(client.getRegistration().getAuthorizationGrantTypes())) {
throw new IllegalStateException("Authorization grant types must not be empty.");
}
}
/**
* Authorization Server endpoints.
*/
public static class Endpoint {
/**
* Authorization Server's OAuth 2.0 Authorization Endpoint.
*/
private String authorizationUri;
/**
* Authorization Server's OAuth 2.0 Token Endpoint.
*/
private String tokenUri;
/**
* Authorization Server's JWK Set Endpoint.
*/
private String jwkSetUri;
/**
* Authorization Server's OAuth 2.0 Token Revocation Endpoint.
*/
private String tokenRevocationUri;
/**
* Authorization Server's OAuth 2.0 Token Introspection Endpoint.
*/
private String tokenIntrospectionUri;
/**
* OpenID Connect 1.0 endpoints.
*/
@NestedConfigurationProperty
private final OidcEndpoint oidc = new OidcEndpoint();
public String getAuthorizationUri() {
return this.authorizationUri;
}
public void setAuthorizationUri(String authorizationUri) {
this.authorizationUri = authorizationUri;
}
public String getTokenUri() {
return this.tokenUri;
}
public void setTokenUri(String tokenUri) {
this.tokenUri = tokenUri;
}
public String getJwkSetUri() {
return this.jwkSetUri;
}
public void setJwkSetUri(String jwkSetUri) {
this.jwkSetUri = jwkSetUri;
}
public String getTokenRevocationUri() {
return this.tokenRevocationUri;
}
public void setTokenRevocationUri(String tokenRevocationUri) {
this.tokenRevocationUri = tokenRevocationUri;
}
public String getTokenIntrospectionUri() {
return this.tokenIntrospectionUri;
}
public void setTokenIntrospectionUri(String tokenIntrospectionUri) {
this.tokenIntrospectionUri = tokenIntrospectionUri;
}
public OidcEndpoint getOidc() {
return this.oidc;
}
}
/**
* OpenID Connect 1.0 endpoints.
*/
public static class OidcEndpoint {
/**
* Authorization Server's OpenID Connect 1.0 Logout Endpoint.
*/
private String logoutUri;
/**
* Authorization Server's OpenID Connect 1.0 Client Registration Endpoint.
*/
private String clientRegistrationUri;
/**
* Authorization Server's OpenID Connect 1.0 UserInfo Endpoint.
*/
private String userInfoUri;
public String getLogoutUri() {
return this.logoutUri;
}
public void setLogoutUri(String logoutUri) {
this.logoutUri = logoutUri;
}
public String getClientRegistrationUri() {
return this.clientRegistrationUri;
}
public void setClientRegistrationUri(String clientRegistrationUri) {
this.clientRegistrationUri = clientRegistrationUri;
}
public String getUserInfoUri() {
return this.userInfoUri;
}
public void setUserInfoUri(String userInfoUri) {
this.userInfoUri = userInfoUri;
}
}
/**
* A registered client of the Authorization Server.
*/
public static class Client {
/**
* Client registration information.
*/
@NestedConfigurationProperty
private final Registration registration = new Registration();
/**
* Whether the client is required to provide a proof key challenge and verifier
* when performing the Authorization Code Grant flow.
*/
private boolean requireProofKey;
/**
* Whether authorization consent is required when the client requests access.
*/
private boolean requireAuthorizationConsent;
/**
* URL for the client's JSON Web Key Set.
*/
private String jwkSetUri;
/**
* JWS algorithm that must be used for signing the JWT used to authenticate the
* client at the Token Endpoint for the {@code private_key_jwt} and
* {@code client_secret_jwt} authentication methods.
*/
private String tokenEndpointAuthenticationSigningAlgorithm;
/**
* Token settings of the registered client.
*/
@NestedConfigurationProperty
private final Token token = new Token();
public Registration getRegistration() {
return this.registration;
}
public boolean isRequireProofKey() {
return this.requireProofKey;
}
public void setRequireProofKey(boolean requireProofKey) {
this.requireProofKey = requireProofKey;
}
public boolean isRequireAuthorizationConsent() {
return this.requireAuthorizationConsent;
}
public void setRequireAuthorizationConsent(boolean requireAuthorizationConsent) {
this.requireAuthorizationConsent = requireAuthorizationConsent;
}
public String getJwkSetUri() {
return this.jwkSetUri;
}
public void setJwkSetUri(String jwkSetUri) {
this.jwkSetUri = jwkSetUri;
}
public String getTokenEndpointAuthenticationSigningAlgorithm() {
return this.tokenEndpointAuthenticationSigningAlgorithm;
}
public void setTokenEndpointAuthenticationSigningAlgorithm(String tokenEndpointAuthenticationSigningAlgorithm) {
this.tokenEndpointAuthenticationSigningAlgorithm = tokenEndpointAuthenticationSigningAlgorithm;
}
public Token getToken() {
return this.token;
}
}
/**
* Client registration information.
*/
public static class Registration {
/**
* Client ID of the registration.
*/
private String clientId;
/**
* Client secret of the registration. May be left blank for a public client.
*/
private String clientSecret;
/**
* Name of the client.
*/
private String clientName;
/**
* Client authentication method(s) that the client may use.
*/
private Set<String> clientAuthenticationMethods = new HashSet<>();
/**
* Authorization grant type(s) that the client may use.
*/
private Set<String> authorizationGrantTypes = new HashSet<>();
/**
* Redirect URI(s) that the client may use in redirect-based flows.
*/
private Set<String> redirectUris = new HashSet<>();
/**
* Redirect URI(s) that the client may use for logout.
*/
private Set<String> postLogoutRedirectUris = new HashSet<>();
/**
* Scope(s) that the client may use.
*/
private Set<String> scopes = new HashSet<>();
public String getClientId() {
return this.clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return this.clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getClientName() {
return this.clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public Set<String> getClientAuthenticationMethods() {
return this.clientAuthenticationMethods;
}
public void setClientAuthenticationMethods(Set<String> clientAuthenticationMethods) {
this.clientAuthenticationMethods = clientAuthenticationMethods;
}
public Set<String> getAuthorizationGrantTypes() {
return this.authorizationGrantTypes;
}
public void setAuthorizationGrantTypes(Set<String> authorizationGrantTypes) {
this.authorizationGrantTypes = authorizationGrantTypes;
}
public Set<String> getRedirectUris() {
return this.redirectUris;
}
public void setRedirectUris(Set<String> redirectUris) {
this.redirectUris = redirectUris;
}
public Set<String> getPostLogoutRedirectUris() {
return this.postLogoutRedirectUris;
}
public void setPostLogoutRedirectUris(Set<String> postLogoutRedirectUris) {
this.postLogoutRedirectUris = postLogoutRedirectUris;
}
public Set<String> getScopes() {
return this.scopes;
}
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
}
/**
* Token settings of the registered client.
*/
public static class Token {
/**
* Time-to-live for an authorization code.
*/
private Duration authorizationCodeTimeToLive;
/**
* Time-to-live for an access token.
*/
private Duration accessTokenTimeToLive;
/**
* Token format for an access token.
*/
private String accessTokenFormat;
/**
* Whether refresh tokens are reused or a new refresh token is issued when
* returning the access token response.
*/
private boolean reuseRefreshTokens;
/**
* Time-to-live for a refresh token.
*/
private Duration refreshTokenTimeToLive;
/**
* JWS algorithm for signing the ID Token.
*/
private String idTokenSignatureAlgorithm;
public Duration getAuthorizationCodeTimeToLive() {
return this.authorizationCodeTimeToLive;
}
public void setAuthorizationCodeTimeToLive(Duration authorizationCodeTimeToLive) {
this.authorizationCodeTimeToLive = authorizationCodeTimeToLive;
}
public Duration getAccessTokenTimeToLive() {
return this.accessTokenTimeToLive;
}
public void setAccessTokenTimeToLive(Duration accessTokenTimeToLive) {
this.accessTokenTimeToLive = accessTokenTimeToLive;
}
public String getAccessTokenFormat() {
return this.accessTokenFormat;
}
public void setAccessTokenFormat(String accessTokenFormat) {
this.accessTokenFormat = accessTokenFormat;
}
public boolean isReuseRefreshTokens() {
return this.reuseRefreshTokens;
}
public void setReuseRefreshTokens(boolean reuseRefreshTokens) {
this.reuseRefreshTokens = reuseRefreshTokens;
}
public Duration getRefreshTokenTimeToLive() {
return this.refreshTokenTimeToLive;
}
public void setRefreshTokenTimeToLive(Duration refreshTokenTimeToLive) {
this.refreshTokenTimeToLive = refreshTokenTimeToLive;
}
public String getIdTokenSignatureAlgorithm() {
return this.idTokenSignatureAlgorithm;
}
public void setIdTokenSignatureAlgorithm(String idTokenSignatureAlgorithm) {
this.idTokenSignatureAlgorithm = idTokenSignatureAlgorithm;
}
}
}

@ -0,0 +1,115 @@
/*
* 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.server;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.security.oauth2.server.OAuth2AuthorizationServerProperties.Client;
import org.springframework.boot.autoconfigure.security.oauth2.server.OAuth2AuthorizationServerProperties.Registration;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
/**
* Adapter class to convert {@link Client} to a {@link RegisteredClient}.
*
* @author Steve Riesenberg
* @since 3.1.0
*/
public final class OAuth2AuthorizationServerPropertiesRegistrationAdapter {
private OAuth2AuthorizationServerPropertiesRegistrationAdapter() {
}
public static List<RegisteredClient> getRegisteredClients(OAuth2AuthorizationServerProperties properties) {
List<RegisteredClient> registeredClients = new ArrayList<>();
properties.getClient()
.forEach((registrationId, client) -> registeredClients.add(getRegisteredClient(registrationId, client)));
return registeredClients;
}
private static RegisteredClient getRegisteredClient(String registrationId, Client client) {
Registration registration = client.getRegistration();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
RegisteredClient.Builder builder = RegisteredClient.withId(registrationId);
map.from(registration::getClientId).to(builder::clientId);
map.from(registration::getClientSecret).to(builder::clientSecret);
map.from(registration::getClientName).to(builder::clientName);
registration.getClientAuthenticationMethods()
.forEach((clientAuthenticationMethod) -> map.from(clientAuthenticationMethod)
.as(ClientAuthenticationMethod::new)
.to(builder::clientAuthenticationMethod));
registration.getAuthorizationGrantTypes()
.forEach((authorizationGrantType) -> map.from(authorizationGrantType)
.as(AuthorizationGrantType::new)
.to(builder::authorizationGrantType));
registration.getRedirectUris().forEach((redirectUri) -> map.from(redirectUri).to(builder::redirectUri));
registration.getPostLogoutRedirectUris()
.forEach((redirectUri) -> map.from(redirectUri).to(builder::postLogoutRedirectUri));
registration.getScopes().forEach((scope) -> map.from(scope).to(builder::scope));
builder.clientSettings(getClientSettings(client, map));
builder.tokenSettings(getTokenSettings(client, map));
return builder.build();
}
private static ClientSettings getClientSettings(Client client, PropertyMapper map) {
ClientSettings.Builder builder = ClientSettings.builder();
map.from(client::isRequireProofKey).to(builder::requireProofKey);
map.from(client::isRequireAuthorizationConsent).to(builder::requireAuthorizationConsent);
map.from(client::getJwkSetUri).to(builder::jwkSetUrl);
map.from(client::getTokenEndpointAuthenticationSigningAlgorithm)
.as(OAuth2AuthorizationServerPropertiesRegistrationAdapter::jwsAlgorithm)
.to(builder::tokenEndpointAuthenticationSigningAlgorithm);
return builder.build();
}
private static TokenSettings getTokenSettings(Client client, PropertyMapper map) {
OAuth2AuthorizationServerProperties.Token token = client.getToken();
TokenSettings.Builder builder = TokenSettings.builder();
map.from(token::getAuthorizationCodeTimeToLive).to(builder::authorizationCodeTimeToLive);
map.from(token::getAccessTokenTimeToLive).to(builder::accessTokenTimeToLive);
map.from(token::getAccessTokenFormat).as(OAuth2TokenFormat::new).to(builder::accessTokenFormat);
map.from(token::isReuseRefreshTokens).to(builder::reuseRefreshTokens);
map.from(token::getRefreshTokenTimeToLive).to(builder::refreshTokenTimeToLive);
map.from(token::getIdTokenSignatureAlgorithm)
.as(OAuth2AuthorizationServerPropertiesRegistrationAdapter::signatureAlgorithm)
.to(builder::idTokenSignatureAlgorithm);
return builder.build();
}
private static JwsAlgorithm jwsAlgorithm(String signingAlgorithm) {
String name = signingAlgorithm.toUpperCase();
JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.from(name);
if (jwsAlgorithm == null) {
jwsAlgorithm = MacAlgorithm.from(name);
}
return jwsAlgorithm;
}
private static SignatureAlgorithm signatureAlgorithm(String signatureAlgorithm) {
return SignatureAlgorithm.from(signatureAlgorithm.toUpperCase());
}
}

@ -0,0 +1,52 @@
/*
* 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.server;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
/**
* Adapter class to convert {@link OAuth2AuthorizationServerProperties.Endpoint} to a
* {@link AuthorizationServerSettings}.
*
* @author Steve Riesenberg
* @since 3.1.0
*/
public final class OAuth2AuthorizationServerPropertiesSettingsAdapter {
private OAuth2AuthorizationServerPropertiesSettingsAdapter() {
}
public static AuthorizationServerSettings getAuthorizationServerSettings(
OAuth2AuthorizationServerProperties properties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
OAuth2AuthorizationServerProperties.Endpoint endpoint = properties.getEndpoint();
OAuth2AuthorizationServerProperties.OidcEndpoint oidc = endpoint.getOidc();
AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder();
map.from(properties::getIssuer).to(builder::issuer);
map.from(endpoint::getAuthorizationUri).to(builder::authorizationEndpoint);
map.from(endpoint::getTokenUri).to(builder::tokenEndpoint);
map.from(endpoint::getJwkSetUri).to(builder::jwkSetEndpoint);
map.from(endpoint::getTokenRevocationUri).to(builder::tokenRevocationEndpoint);
map.from(endpoint::getTokenIntrospectionUri).to(builder::tokenIntrospectionEndpoint);
map.from(oidc::getLogoutUri).to(builder::oidcLogoutEndpoint);
map.from(oidc::getClientRegistrationUri).to(builder::oidcClientRegistrationEndpoint);
map.from(oidc::getUserInfoUri).to(builder::oidcUserInfoEndpoint);
return builder.build();
}
}

@ -0,0 +1,66 @@
/*
* 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.server;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Condition that matches if any {@code spring.security.oauth2.authorizationserver.client}
* properties are defined.
*
* @author Steve Riesenberg
* @since 3.1.0
*/
public class RegisteredClientsConfiguredCondition extends SpringBootCondition {
private static final Bindable<Map<String, OAuth2AuthorizationServerProperties.Client>> STRING_CLIENT_MAP = Bindable
.mapOf(String.class, OAuth2AuthorizationServerProperties.Client.class);
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth2 Registered Clients Configured Condition");
Map<String, OAuth2AuthorizationServerProperties.Client> registrations = getRegistrations(
context.getEnvironment());
if (!registrations.isEmpty()) {
return ConditionOutcome.match(message.foundExactly("registered clients " + registrations.values()
.stream()
.map(OAuth2AuthorizationServerProperties.Client::getRegistration)
.map(OAuth2AuthorizationServerProperties.Registration::getClientId)
.collect(Collectors.joining(", "))));
}
return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
}
private Map<String, OAuth2AuthorizationServerProperties.Client> getRegistrations(Environment environment) {
return Binder.get(environment)
.bind("spring.security.oauth2.authorizationserver.client", STRING_CLIENT_MAP)
.orElse(Collections.emptyMap());
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Support for Spring Security's OAuth2 authorization server.
*/
package org.springframework.boot.autoconfigure.security.oauth2.server;

@ -0,0 +1,52 @@
/*
* 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.server.servlet;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OAuth2 authorization server
* support.
*
* <p>
* <strong>Note:</strong> This configuration and
* {@link OAuth2AuthorizationServerJwtAutoConfiguration} work together to ensure that the
* {@link org.springframework.security.config.annotation.ObjectPostProcessor} is defined
* <strong>BEFORE</strong> {@link UserDetailsServiceAutoConfiguration} so that a
* {@link org.springframework.security.core.userdetails.UserDetailsService} can be created
* if necessary.
*
* @author Steve Riesenberg
* @since 3.1.0
* @see OAuth2AuthorizationServerJwtAutoConfiguration
*/
@AutoConfiguration(before = { OAuth2ResourceServerAutoConfiguration.class, SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class })
@ConditionalOnClass(OAuth2Authorization.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ OAuth2AuthorizationServerConfiguration.class, OAuth2AuthorizationServerWebSecurityConfiguration.class })
public class OAuth2AuthorizationServerAutoConfiguration {
}

@ -0,0 +1,60 @@
/*
* 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.server.servlet;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.oauth2.server.OAuth2AuthorizationServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.server.OAuth2AuthorizationServerPropertiesRegistrationAdapter;
import org.springframework.boot.autoconfigure.security.oauth2.server.OAuth2AuthorizationServerPropertiesSettingsAdapter;
import org.springframework.boot.autoconfigure.security.oauth2.server.RegisteredClientsConfiguredCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
/**
* {@link Configuration @Configuration} used to map
* {@link OAuth2AuthorizationServerProperties} to registered clients and settings.
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2AuthorizationServerProperties.class)
class OAuth2AuthorizationServerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(RegisteredClientsConfiguredCondition.class)
RegisteredClientRepository registeredClientRepository(OAuth2AuthorizationServerProperties properties) {
List<RegisteredClient> registeredClients = OAuth2AuthorizationServerPropertiesRegistrationAdapter
.getRegisteredClients(properties);
return new InMemoryRegisteredClientRepository(registeredClients);
}
@Bean
@ConditionalOnMissingBean
AuthorizationServerSettings authorizationServerSettings(OAuth2AuthorizationServerProperties properties) {
return OAuth2AuthorizationServerPropertiesSettingsAdapter.getAuthorizationServerSettings(properties);
}
}

@ -0,0 +1,98 @@
/*
* 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.server.servlet;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Role;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for JWT support for endpoints of the
* OAuth2 authorization server that require it (e.g. User Info, Client Registration).
*
* @author Steve Riesenberg
* @since 3.1.0
*/
@AutoConfiguration(after = UserDetailsServiceAutoConfiguration.class)
@ConditionalOnClass(OAuth2Authorization.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class OAuth2AuthorizationServerJwtAutoConfiguration {
@Bean
@ConditionalOnClass(JwtDecoder.class)
@ConditionalOnMissingBean
JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean
JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = getRsaKey();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static RSAKey getRsaKey() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
return rsaKey;
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}

@ -0,0 +1,74 @@
/*
* 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.server.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
/**
* {@link Configuration @Configuration} for OAuth2 authorization server support.
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnBean({ RegisteredClientRepository.class, AuthorizationServerSettings.class })
class OAuth2AuthorizationServerWebSecurityConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
// @formatter:off
http
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
);
// @formatter:on
return http.build();
}
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for Spring Security's OAuth2 authorization server.
*/
package org.springframework.boot.autoconfigure.security.oauth2.server.servlet;

@ -37,7 +37,7 @@ import org.springframework.security.authentication.DefaultAuthenticationEventPub
* @author Madhura Bhave
* @since 1.0.0
*/
@AutoConfiguration
@AutoConfiguration(before = UserDetailsServiceAutoConfiguration.class)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })

@ -112,6 +112,8 @@ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2Clie
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration

@ -0,0 +1,93 @@
/*
* 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.server;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerPropertiesRegistrationAdapter}.
*
* @author Steve Riesenberg
*/
class OAuth2AuthorizationServerPropertiesRegistrationAdapterTests {
@Test
void getRegisteredClientsWhenValidParametersShouldAdapt() {
OAuth2AuthorizationServerProperties properties = new OAuth2AuthorizationServerProperties();
OAuth2AuthorizationServerProperties.Client client = createClient();
properties.getClient().put("foo", client);
List<RegisteredClient> registeredClients = OAuth2AuthorizationServerPropertiesRegistrationAdapter
.getRegisteredClients(properties);
assertThat(registeredClients).hasSize(1);
RegisteredClient registeredClient = registeredClients.get(0);
assertThat(registeredClient.getClientId()).isEqualTo("foo");
assertThat(registeredClient.getClientSecret()).isEqualTo("secret");
assertThat(registeredClient.getClientAuthenticationMethods())
.containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
assertThat(registeredClient.getAuthorizationGrantTypes())
.containsExactly(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registeredClient.getRedirectUris()).containsExactly("https://example.com/redirect");
assertThat(registeredClient.getPostLogoutRedirectUris()).containsExactly("https://example.com/logout");
assertThat(registeredClient.getScopes()).containsExactly("user.read");
assertThat(registeredClient.getClientSettings().isRequireProofKey()).isTrue();
assertThat(registeredClient.getClientSettings().isRequireAuthorizationConsent()).isTrue();
assertThat(registeredClient.getClientSettings().getJwkSetUrl()).isEqualTo("https://example.com/jwks");
assertThat(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256);
assertThat(registeredClient.getTokenSettings().getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.REFERENCE);
assertThat(registeredClient.getTokenSettings().getAccessTokenTimeToLive()).isEqualTo(Duration.ofSeconds(300));
assertThat(registeredClient.getTokenSettings().getRefreshTokenTimeToLive()).isEqualTo(Duration.ofHours(24));
assertThat(registeredClient.getTokenSettings().isReuseRefreshTokens()).isEqualTo(true);
assertThat(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm())
.isEqualTo(SignatureAlgorithm.RS512);
}
private OAuth2AuthorizationServerProperties.Client createClient() {
OAuth2AuthorizationServerProperties.Client client = new OAuth2AuthorizationServerProperties.Client();
client.setRequireProofKey(true);
client.setRequireAuthorizationConsent(true);
client.setJwkSetUri("https://example.com/jwks");
client.setTokenEndpointAuthenticationSigningAlgorithm("rs256");
OAuth2AuthorizationServerProperties.Registration registration = client.getRegistration();
registration.setClientId("foo");
registration.setClientSecret("secret");
registration.getClientAuthenticationMethods().add("client_secret_basic");
registration.getAuthorizationGrantTypes().add("authorization_code");
registration.getRedirectUris().add("https://example.com/redirect");
registration.getPostLogoutRedirectUris().add("https://example.com/logout");
registration.getScopes().add("user.read");
OAuth2AuthorizationServerProperties.Token token = client.getToken();
token.setAccessTokenFormat("reference");
token.setAccessTokenTimeToLive(Duration.ofSeconds(300));
token.setRefreshTokenTimeToLive(Duration.ofHours(24));
token.setReuseRefreshTokens(true);
token.setIdTokenSignatureAlgorithm("rs512");
return client;
}
}

@ -0,0 +1,64 @@
/*
* 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.server;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerPropertiesRegistrationAdapter}.
*
* @author Steve Riesenberg
*/
class OAuth2AuthorizationServerPropertiesSettingsAdapterTests {
@Test
void getAuthorizationServerSettingsWhenValidParametersShouldAdapt() {
OAuth2AuthorizationServerProperties properties = createAuthorizationServerProperties();
AuthorizationServerSettings settings = OAuth2AuthorizationServerPropertiesSettingsAdapter
.getAuthorizationServerSettings(properties);
assertThat(settings.getIssuer()).isEqualTo("https://example.com");
assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize");
assertThat(settings.getTokenEndpoint()).isEqualTo("/token");
assertThat(settings.getJwkSetEndpoint()).isEqualTo("/jwks");
assertThat(settings.getTokenRevocationEndpoint()).isEqualTo("/revoke");
assertThat(settings.getTokenIntrospectionEndpoint()).isEqualTo("/introspect");
assertThat(settings.getOidcLogoutEndpoint()).isEqualTo("/logout");
assertThat(settings.getOidcClientRegistrationEndpoint()).isEqualTo("/register");
assertThat(settings.getOidcUserInfoEndpoint()).isEqualTo("/user");
}
private OAuth2AuthorizationServerProperties createAuthorizationServerProperties() {
OAuth2AuthorizationServerProperties properties = new OAuth2AuthorizationServerProperties();
properties.setIssuer("https://example.com");
OAuth2AuthorizationServerProperties.Endpoint endpoints = properties.getEndpoint();
endpoints.setAuthorizationUri("/authorize");
endpoints.setTokenUri("/token");
endpoints.setJwkSetUri("/jwks");
endpoints.setTokenRevocationUri("/revoke");
endpoints.setTokenIntrospectionUri("/introspect");
OAuth2AuthorizationServerProperties.OidcEndpoint oidc = endpoints.getOidc();
oidc.setLogoutUri("/logout");
oidc.setClientRegistrationUri("/register");
oidc.setUserInfoUri("/user");
return properties;
}
}

@ -0,0 +1,72 @@
/*
* 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.server;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link OAuth2AuthorizationServerProperties}.
*
* @author Steve Riesenberg
*/
class OAuth2AuthorizationServerPropertiesTests {
private final OAuth2AuthorizationServerProperties properties = new OAuth2AuthorizationServerProperties();
@Test
void clientIdAbsentThrowsException() {
OAuth2AuthorizationServerProperties.Client client = new OAuth2AuthorizationServerProperties.Client();
client.getRegistration().getClientAuthenticationMethods().add("client_secret_basic");
client.getRegistration().getAuthorizationGrantTypes().add("authorization_code");
this.properties.getClient().put("foo", client);
assertThatIllegalStateException().isThrownBy(this.properties::validate)
.withMessage("Client id must not be empty.");
}
@Test
void clientSecretAbsentShouldNotThrowException() {
OAuth2AuthorizationServerProperties.Client client = new OAuth2AuthorizationServerProperties.Client();
client.getRegistration().setClientId("foo");
client.getRegistration().getClientAuthenticationMethods().add("client_secret_basic");
client.getRegistration().getAuthorizationGrantTypes().add("authorization_code");
this.properties.getClient().put("foo", client);
this.properties.validate();
}
@Test
void clientAuthenticationMethodsEmptyThrowsException() {
OAuth2AuthorizationServerProperties.Client client = new OAuth2AuthorizationServerProperties.Client();
client.getRegistration().setClientId("foo");
client.getRegistration().getAuthorizationGrantTypes().add("authorization_code");
this.properties.getClient().put("foo", client);
assertThatIllegalStateException().isThrownBy(this.properties::validate)
.withMessage("Client authentication methods must not be empty.");
}
@Test
void authorizationGrantTypesEmptyThrowsException() {
OAuth2AuthorizationServerProperties.Client client = new OAuth2AuthorizationServerProperties.Client();
client.getRegistration().setClientId("foo");
client.getRegistration().getClientAuthenticationMethods().add("client_secret_basic");
this.properties.getClient().put("foo", client);
assertThatIllegalStateException().isThrownBy(this.properties::validate)
.withMessage("Authorization grant types must not be empty.");
}
}

@ -0,0 +1,184 @@
/*
* 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.server.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link OAuth2AuthorizationServerAutoConfiguration}.
*
* @author Steve Riesenberg
* @author Madhura Bhave
*/
class OAuth2AuthorizationServerAutoConfigurationTests {
private static final String PROPERTIES_PREFIX = "spring.security.oauth2.authorizationserver";
private static final String CLIENT_PREFIX = PROPERTIES_PREFIX + ".client";
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OAuth2AuthorizationServerAutoConfiguration.class,
OAuth2AuthorizationServerJwtAutoConfiguration.class, SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class));
@Test
void autoConfigurationConditionalOnClassOauth2Authorization() {
this.contextRunner.withClassLoader(new FilteredClassLoader(OAuth2Authorization.class))
.run((context) -> assertThat(context).doesNotHaveBean(OAuth2AuthorizationServerAutoConfiguration.class));
}
@Test
void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() {
this.contextRunner.run((context) -> assertThat(context).hasBean("inMemoryUserDetailsManager"));
}
@Test
void registeredClientRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(RegisteredClientRepository.class));
}
@Test
void registeredClientRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner
.withPropertyValues(CLIENT_PREFIX + ".foo.registration.client-id=abcd",
CLIENT_PREFIX + ".foo.registration.client-secret=secret",
CLIENT_PREFIX + ".foo.registration.client-authentication-methods=client_secret_basic",
CLIENT_PREFIX + ".foo.registration.authorization-grant-types=client_credentials",
CLIENT_PREFIX + ".foo.registration.scopes=test")
.run((context) -> {
RegisteredClientRepository registeredClientRepository = context
.getBean(RegisteredClientRepository.class);
RegisteredClient registeredClient = registeredClientRepository.findById("foo");
assertThat(registeredClient).isNotNull();
assertThat(registeredClient.getClientId()).isEqualTo("abcd");
assertThat(registeredClient.getClientSecret()).isEqualTo("secret");
assertThat(registeredClient.getClientAuthenticationMethods())
.containsOnly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
assertThat(registeredClient.getAuthorizationGrantTypes())
.containsOnly(AuthorizationGrantType.CLIENT_CREDENTIALS);
assertThat(registeredClient.getScopes()).containsOnly("test");
});
}
@Test
void registeredClientRepositoryBacksOffWhenRegisteredClientRepositoryBeanPresent() {
this.contextRunner.withUserConfiguration(TestRegisteredClientRepositoryConfiguration.class)
.withPropertyValues(CLIENT_PREFIX + ".foo.registration.client-id=abcd",
CLIENT_PREFIX + ".foo.registration.client-secret=secret",
CLIENT_PREFIX + ".foo.registration.client-authentication-methods=client_secret_basic",
CLIENT_PREFIX + ".foo.registration.authorization-grant-types=client_credentials",
CLIENT_PREFIX + ".foo.registration.scope=test")
.run((context) -> {
RegisteredClientRepository registeredClientRepository = context
.getBean(RegisteredClientRepository.class);
RegisteredClient registeredClient = registeredClientRepository.findById("test");
assertThat(registeredClient).isNotNull();
assertThat(registeredClient.getClientId()).isEqualTo("abcd");
assertThat(registeredClient.getClientSecret()).isEqualTo("secret");
assertThat(registeredClient.getClientAuthenticationMethods())
.containsOnly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
assertThat(registeredClient.getAuthorizationGrantTypes())
.containsOnly(AuthorizationGrantType.CLIENT_CREDENTIALS);
assertThat(registeredClient.getScopes()).containsOnly("test");
});
}
@Test
void authorizationServerSettingsBeanShouldBeCreatedWhenPropertiesAbsent() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(AuthorizationServerSettings.class));
}
@Test
void authorizationServerSettingsBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner
.withPropertyValues(PROPERTIES_PREFIX + ".issuer=https://example.com",
PROPERTIES_PREFIX + ".endpoint.authorization-uri=/authorize",
PROPERTIES_PREFIX + ".endpoint.token-uri=/token", PROPERTIES_PREFIX + ".endpoint.jwk-set-uri=/jwks",
PROPERTIES_PREFIX + ".endpoint.token-revocation-uri=/revoke",
PROPERTIES_PREFIX + ".endpoint.token-introspection-uri=/introspect",
PROPERTIES_PREFIX + ".endpoint.oidc.logout-uri=/logout",
PROPERTIES_PREFIX + ".endpoint.oidc.client-registration-uri=/register",
PROPERTIES_PREFIX + ".endpoint.oidc.user-info-uri=/user")
.run((context) -> {
AuthorizationServerSettings settings = context.getBean(AuthorizationServerSettings.class);
assertThat(settings.getIssuer()).isEqualTo("https://example.com");
assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize");
assertThat(settings.getTokenEndpoint()).isEqualTo("/token");
assertThat(settings.getJwkSetEndpoint()).isEqualTo("/jwks");
assertThat(settings.getTokenRevocationEndpoint()).isEqualTo("/revoke");
assertThat(settings.getTokenIntrospectionEndpoint()).isEqualTo("/introspect");
assertThat(settings.getOidcLogoutEndpoint()).isEqualTo("/logout");
assertThat(settings.getOidcClientRegistrationEndpoint()).isEqualTo("/register");
assertThat(settings.getOidcUserInfoEndpoint()).isEqualTo("/user");
});
}
@Test
void authorizationServerSettingsBacksOffWhenAuthorizationServerSettingsBeanPresent() {
this.contextRunner.withUserConfiguration(TestAuthorizationServerSettingsConfiguration.class)
.withPropertyValues(PROPERTIES_PREFIX + ".issuer=https://test.com")
.run((context) -> {
AuthorizationServerSettings settings = context.getBean(AuthorizationServerSettings.class);
assertThat(settings.getIssuer()).isEqualTo("https://example.com");
});
}
@Configuration
static class TestRegisteredClientRepositoryConfiguration {
@Bean
RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId("test")
.clientId("abcd")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("test")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
}
@Configuration
static class TestAuthorizationServerSettingsConfiguration {
@Bean
AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().issuer("https://example.com").build();
}
}
}

@ -0,0 +1,107 @@
/*
* 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.server.servlet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerJwtAutoConfiguration}.
*
* @author Steve Riesenberg
*/
class OAuth2AuthorizationServerJwtAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OAuth2AuthorizationServerJwtAutoConfiguration.class));
@Test
void autoConfigurationConditionalOnClassOauth2Authorization() {
this.contextRunner.withClassLoader(new FilteredClassLoader(OAuth2Authorization.class))
.run((context) -> assertThat(context).doesNotHaveBean(OAuth2AuthorizationServerJwtAutoConfiguration.class));
}
@Test
void jwtDecoderConditionalOnClassJwtDecoder() {
this.contextRunner.withClassLoader(new FilteredClassLoader(JwtDecoder.class))
.run((context) -> assertThat(context).doesNotHaveBean("jwtDecoder"));
}
@Test
void jwtConfigurationConfiguresJwtDecoderWithGeneratedKey() {
this.contextRunner.run((context) -> {
assertThat(context).hasBean("jwtDecoder");
assertThat(context.getBean("jwtDecoder")).isInstanceOf(NimbusJwtDecoder.class);
assertThat(context).hasBean("jwkSource");
assertThat(context.getBean("jwkSource")).isInstanceOf(ImmutableJWKSet.class);
});
}
@Test
void jwtDecoderBacksOffWhenBeanPresent() {
this.contextRunner.withUserConfiguration(TestJwtDecoderConfiguration.class).run((context) -> {
assertThat(context).hasBean("jwtDecoder");
assertThat(context.getBean("jwtDecoder")).isNotInstanceOf(NimbusJwtDecoder.class);
assertThat(context).hasBean("jwkSource");
assertThat(context.getBean("jwkSource")).isInstanceOf(ImmutableJWKSet.class);
});
}
@Test
void jwkSourceBacksOffWhenBeanPresent() {
this.contextRunner.withUserConfiguration(TestJwkSourceConfiguration.class).run((context) -> {
assertThat(context).hasBean("jwtDecoder");
assertThat(context.getBean("jwtDecoder")).isInstanceOf(NimbusJwtDecoder.class);
assertThat(context).hasBean("jwkSource");
assertThat(context.getBean("jwkSource")).isNotInstanceOf(ImmutableJWKSet.class);
});
}
@Configuration
static class TestJwtDecoderConfiguration {
@Bean
JwtDecoder jwtDecoder() {
return (token) -> null;
}
}
@Configuration
static class TestJwkSourceConfiguration {
@Bean
JWKSource<SecurityContext> jwkSource() {
return (jwkSelector, context) -> null;
}
}
}

@ -0,0 +1,178 @@
/*
* 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.server.servlet;
import java.util.List;
import jakarta.servlet.Filter;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerWebSecurityConfiguration}.
*
* @author Steve Riesenberg
*/
class OAuth2AuthorizationServerWebSecurityConfigurationTests {
private static final String PROPERTIES_PREFIX = "spring.security.oauth2.authorizationserver";
private static final String CLIENT_PREFIX = PROPERTIES_PREFIX + ".client";
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner();
@Test
void webSecurityConfigurationConfiguresAuthorizationServerWithFormLogin() {
this.contextRunner.withUserConfiguration(TestOAuth2AuthorizationServerConfiguration.class)
.withPropertyValues(CLIENT_PREFIX + ".foo.registration.client-id=abcd",
CLIENT_PREFIX + ".foo.registration.client-secret=secret",
CLIENT_PREFIX + ".foo.registration.client-authentication-methods=client_secret_basic",
CLIENT_PREFIX + ".foo.registration.authorization-grant-types=client_credentials",
CLIENT_PREFIX + ".foo.registration.scopes=test")
.run((context) -> {
assertThat(context).hasBean("authorizationServerSecurityFilterChain");
assertThat(context).hasBean("defaultSecurityFilterChain");
assertThat(context).hasBean("registeredClientRepository");
assertThat(context).hasBean("authorizationServerSettings");
assertThat(findFilter(context, OAuth2AuthorizationEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OAuth2TokenEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OAuth2TokenIntrospectionEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OAuth2TokenRevocationEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OAuth2AuthorizationServerMetadataEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OidcProviderConfigurationEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OidcUserInfoEndpointFilter.class, 0)).isNotNull();
assertThat(findFilter(context, BearerTokenAuthenticationFilter.class, 0)).isNotNull();
assertThat(findFilter(context, OidcClientRegistrationEndpointFilter.class, 0)).isNull();
assertThat(findFilter(context, UsernamePasswordAuthenticationFilter.class, 0)).isNull();
assertThat(findFilter(context, DefaultLoginPageGeneratingFilter.class, 1)).isNotNull();
assertThat(findFilter(context, UsernamePasswordAuthenticationFilter.class, 1)).isNotNull();
});
}
@Test
void securityFilterChainsBackOffWhenSecurityFilterChainBeanPresent() {
this.contextRunner
.withUserConfiguration(TestSecurityFilterChainConfiguration.class,
TestOAuth2AuthorizationServerConfiguration.class)
.withPropertyValues(CLIENT_PREFIX + ".foo.registration.client-id=abcd",
CLIENT_PREFIX + ".foo.registration.client-secret=secret",
CLIENT_PREFIX + ".foo.registration.client-authentication-methods=client_secret_basic",
CLIENT_PREFIX + ".foo.registration.authorization-grant-types=client_credentials",
CLIENT_PREFIX + ".foo.registration.scopes=test")
.run((context) -> {
assertThat(context).hasBean("authServerSecurityFilterChain");
assertThat(context).doesNotHaveBean("authorizationServerSecurityFilterChain");
assertThat(context).hasBean("securityFilterChain");
assertThat(context).doesNotHaveBean("defaultSecurityFilterChain");
assertThat(context).hasBean("registeredClientRepository");
assertThat(context).hasBean("authorizationServerSettings");
assertThat(findFilter(context, BearerTokenAuthenticationFilter.class, 0)).isNull();
assertThat(findFilter(context, UsernamePasswordAuthenticationFilter.class, 1)).isNull();
});
}
private Filter findFilter(AssertableWebApplicationContext context, Class<? extends Filter> filter,
int filterChainIndex) {
FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
List<Filter> filters = filterChains.get(filterChainIndex).getFilters();
return filters.stream().filter(filter::isInstance).findFirst().orElse(null);
}
@Configuration
@EnableWebSecurity
@Import({ TestRegisteredClientRepositoryConfiguration.class,
OAuth2AuthorizationServerWebSecurityConfiguration.class,
OAuth2AuthorizationServerJwtAutoConfiguration.class })
static class TestOAuth2AuthorizationServerConfiguration {
}
@Configuration
static class TestRegisteredClientRepositoryConfiguration {
@Bean
RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId("test")
.clientId("abcd")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("test")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().issuer("https://example.com").build();
}
}
@Configuration
@EnableWebSecurity
static class TestSecurityFilterChainConfiguration {
@Bean
@Order(1)
SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.build();
}
@Bean
@Order(2)
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.httpBasic(Customizer.withDefaults()).build();
}
}
}

@ -1336,6 +1336,13 @@ bom {
]
}
}
library("Spring Authorization Server", "1.1.0-M2") {
group("org.springframework.security") {
modules = [
"spring-security-oauth2-authorization-server"
]
}
}
library("Spring Batch", "5.0.1") {
group("org.springframework.batch") {
imports = [

@ -320,6 +320,7 @@ tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
"spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"],
"spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"],
"spring-security-version": securityVersion,
"spring-authorization-server-version": versionConstraints["org.springframework.security:spring-security-oauth2-authorization-server"],
"spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"],
"tomcat-version": tomcatVersion.split("\\.").take(2).join('.'),
"remote-spring-application-output": runRemoteSpringApplicationExample.outputs.files.singleFile,

@ -94,6 +94,7 @@
:spring-security: https://spring.io/projects/spring-security
:spring-security-docs: https://docs.spring.io/spring-security/reference/{spring-security-version}
:spring-authorization-server: https://spring.io/projects/spring-authorization-server
:spring-authorization-server-docs: https://docs.spring.io/spring-authorization-server/docs/{spring-authorization-server-version}/reference/html
:spring-session: https://spring.io/projects/spring-session
:spring-webservices-docs: https://docs.spring.io/spring-ws/docs/{spring-webservices-version}/reference/html/
:ant-docs: https://ant.apache.org/manual

@ -227,7 +227,72 @@ Alternatively, you can define your own `OpaqueTokenIntrospector` bean for servle
[[web.security.oauth2.authorization-server]]
==== Authorization Server
You can use the {spring-authorization-server}[Spring Authorization Server] project to implement an OAuth 2.0 Authorization Server.
If you have `spring-security-oauth2-authorization-server` on your classpath, you can take advantage of some auto-configuration to set up an OAuth2 Authorization Server.
This configuration makes use of the properties under `OAuth2AuthorizationServerProperties`.
The properties are only applicable for servlet applications.
You can register multiple OAuth2 clients under the `spring.security.oauth2.authorizationserver.client` prefix, as shown in the following example:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
security:
oauth2:
authorizationserver:
client:
my-client-1:
registration:
client-id: "abcd"
client-secret: "{noop}secret1"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
redirect-uris:
- "https://my-client-1.com/login/oauth2/code/abcd"
- "https://my-client-1.com/authorized"
scopes:
- "openid"
- "profile"
- "email"
- "phone"
- "address"
require-authorization-consent: true
my-client-2:
registration:
client-id: "efgh"
client-secret: "{noop}secret2"
client-authentication-methods:
- "client_secret_jwt"
authorization-grant-types:
- "client_credentials"
scopes:
- "user.read"
- "user.write"
jwk-set-uri: "https://my-client-2.com/jwks"
token-endpoint-authentication-signing-algorithm: "RS256"
----
NOTE: The `client-secret` property must be in a format that can be matched by the configured `PasswordEncoder`.
The default instance of `PasswordEncoder` is created via `PasswordEncoderFactories.createDelegatingPasswordEncoder()`.
The auto-configuration Spring Boot provides for Spring Authorization Server is designed for getting started quickly.
Most applications will require customization and will want to define several beans to override auto-configuration.
The following components can be defined as beans to override auto-configuration specific to Spring Authorization Server:
* `RegisteredClientRepository`
* `AuthorizationServerSettings`
* `SecurityFilterChain`
* `com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>`
* `JwtDecoder`
TIP: Spring Boot auto-configures an `InMemoryRegisteredClientRepository` which is used by Spring Authorization Server for the management of registered clients.
The `InMemoryRegisteredClientRepository` has limited capabilities and we recommend using it only for development environments.
For production environments, consider using a `JdbcRegisteredClientRepository` or creating your own implementation of `RegisteredClientRepository`.
Additional information can be found in the {spring-authorization-server-docs}/getting-started.html[Getting Started] chapter of the {spring-authorization-server-docs}/index.html[Spring Authorization Server Reference Guide].

@ -0,0 +1,10 @@
plugins {
id "org.springframework.boot.starter"
}
description = "Starter for using Spring Authorization Server features"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("org.springframework.security:spring-security-oauth2-authorization-server")
}

@ -0,0 +1,14 @@
plugins {
id "java"
id "org.springframework.boot.conventions"
}
description = "Spring Boot OAuth2 Authorization Server smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-oauth2-authorization-server"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
testImplementation("org.apache.httpcomponents.client5:httpclient5")
}

@ -0,0 +1,29 @@
/*
* 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 smoketest.oauth2.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleOAuth2AuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(SampleOAuth2AuthorizationServerApplication.class);
}
}

@ -0,0 +1,27 @@
spring:
security:
oauth2:
authorizationserver:
issuer: https://provider.com
endpoint:
authorization-uri: /authorize
token-uri: /token
jwk-set-uri: /jwks
token-revocation-uri: /revoke
token-introspection-uri: /introspect
oidc:
logout-uri: /logout
client-registration-uri: /register
user-info-uri: /user
client:
messaging-client:
registration:
client-id: messaging-client
client-secret: "{noop}secret"
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- client_credentials
scopes:
- message.read
- message.write

@ -0,0 +1,121 @@
/*
* 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 smoketest.oauth2.server;
import java.net.URI;
import java.util.Map;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SampleOAuth2AuthorizationServerApplicationTests {
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new ParameterizedTypeReference<>() {
};
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void openidConfigurationShouldAllowAccess() {
ResponseEntity<Map<String, Object>> entity = this.restTemplate.exchange("/.well-known/openid-configuration",
HttpMethod.GET, null, MAP_TYPE_REFERENCE);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
OidcProviderConfiguration config = OidcProviderConfiguration.withClaims(entity.getBody()).build();
assertThat(config.getIssuer().toString()).isEqualTo("https://provider.com");
assertThat(config.getAuthorizationEndpoint().toString()).isEqualTo("https://provider.com/authorize");
assertThat(config.getTokenEndpoint().toString()).isEqualTo("https://provider.com/token");
assertThat(config.getJwkSetUrl().toString()).isEqualTo("https://provider.com/jwks");
assertThat(config.getTokenRevocationEndpoint().toString()).isEqualTo("https://provider.com/revoke");
assertThat(config.getEndSessionEndpoint().toString()).isEqualTo("https://provider.com/logout");
assertThat(config.getTokenIntrospectionEndpoint().toString()).isEqualTo("https://provider.com/introspect");
assertThat(config.getUserInfoEndpoint().toString()).isEqualTo("https://provider.com/user");
// OIDC Client Registration is disabled by default
assertThat(config.getClientRegistrationEndpoint()).isNull();
}
@Test
void authServerMetadataShouldAllowAccess() {
ResponseEntity<Map<String, Object>> entity = this.restTemplate
.exchange("/.well-known/oauth-authorization-server", HttpMethod.GET, null, MAP_TYPE_REFERENCE);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
OAuth2AuthorizationServerMetadata config = OAuth2AuthorizationServerMetadata.withClaims(entity.getBody())
.build();
assertThat(config.getIssuer().toString()).isEqualTo("https://provider.com");
assertThat(config.getAuthorizationEndpoint().toString()).isEqualTo("https://provider.com/authorize");
assertThat(config.getTokenEndpoint().toString()).isEqualTo("https://provider.com/token");
assertThat(config.getJwkSetUrl().toString()).isEqualTo("https://provider.com/jwks");
assertThat(config.getTokenRevocationEndpoint().toString()).isEqualTo("https://provider.com/revoke");
assertThat(config.getTokenIntrospectionEndpoint().toString()).isEqualTo("https://provider.com/introspect");
// OIDC Client Registration is disabled by default
assertThat(config.getClientRegistrationEndpoint()).isNull();
}
@Test
void anonymousShouldRedirectToLogin() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation()).isEqualTo(URI.create("http://localhost:" + this.port + "/login"));
}
@Test
void validTokenRequestShouldReturnTokenResponse() {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("messaging-client", "secret");
HttpEntity<Object> request = new HttpEntity<>(headers);
String requestUri = UriComponentsBuilder.fromUriString("/token")
.queryParam(OAuth2ParameterNames.CLIENT_ID, "messaging-client")
.queryParam(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.queryParam(OAuth2ParameterNames.SCOPE, "message.read+message.write")
.toUriString();
ResponseEntity<Map<String, Object>> entity = this.restTemplate.exchange(requestUri, HttpMethod.POST, request,
MAP_TYPE_REFERENCE);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
Map<String, Object> tokenResponse = Objects.requireNonNull(entity.getBody());
assertThat(tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN)).isNotNull();
assertThat(tokenResponse.get(OAuth2ParameterNames.EXPIRES_IN)).isNotNull();
assertThat(tokenResponse.get(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write");
assertThat(tokenResponse.get(OAuth2ParameterNames.TOKEN_TYPE))
.isEqualTo(OAuth2AccessToken.TokenType.BEARER.getValue());
}
}
Loading…
Cancel
Save