parent
25d77ee70b
commit
e6f602cec0
2
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesConfiguration.java → spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerConfiguration.java
2
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesConfiguration.java → spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerConfiguration.java
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Role;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Configuration @Configuration} for JWT support for endpoints of the OAuth2
|
|
||||||
* authorization server that require it (e.g. User Info, Client Registration).
|
|
||||||
*
|
|
||||||
* @author Steve Riesenberg
|
|
||||||
*/
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
class OAuth2AuthorizationServerJwtConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnClass(JwtDecoder.class)
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
|
||||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
|
||||||
@ConditionalOnClass(JWKSource.class)
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
JWKSource<SecurityContext> jwkSource() {
|
|
||||||
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
|
|
||||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
|
||||||
return new ImmutableJWKSet<>(jwkSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.test.context.runner.ApplicationContextRunner;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link OAuth2AuthorizationServerJwtConfiguration}.
|
|
||||||
*
|
|
||||||
* @author Steve Riesenberg
|
|
||||||
*/
|
|
||||||
public class OAuth2AuthorizationServerJwtConfigurationTests {
|
|
||||||
|
|
||||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void jwtConfigurationConfiguresJwtDecoderWithGeneratedKey() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestJwtConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasBean("jwtDecoder");
|
|
||||||
assertThat(context).hasBean("jwkSource");
|
|
||||||
|
|
||||||
assertThat(context.getBean("jwtDecoder")).isInstanceOf(NimbusJwtDecoder.class);
|
|
||||||
assertThat(context.getBean("jwkSource")).isInstanceOf(ImmutableJWKSet.class);
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void jwtDecoderBacksOffWhenBeanPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestJwtDecoderConfiguration.class, TestJwtConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasBean("jwtDecoder");
|
|
||||||
assertThat(context).hasBean("jwkSource");
|
|
||||||
|
|
||||||
assertThat(context.getBean("jwtDecoder")).isNotInstanceOf(NimbusJwtDecoder.class);
|
|
||||||
assertThat(context.getBean("jwkSource")).isInstanceOf(ImmutableJWKSet.class);
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void jwkSourceBacksOffWhenBeanPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestJwkSourceConfiguration.class, TestJwtConfiguration.class)
|
|
||||||
.run((context) -> {
|
|
||||||
assertThat(context).hasBean("jwtDecoder");
|
|
||||||
assertThat(context).hasBean("jwkSource");
|
|
||||||
|
|
||||||
assertThat(context.getBean("jwtDecoder")).isInstanceOf(NimbusJwtDecoder.class);
|
|
||||||
assertThat(context.getBean("jwkSource")).isNotInstanceOf(ImmutableJWKSet.class);
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Import(OAuth2AuthorizationServerJwtConfiguration.class)
|
|
||||||
static class TestJwtConfiguration {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class TestJwtDecoderConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
JwtDecoder jwtDecoder() {
|
|
||||||
return (token) -> null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class TestJwkSourceConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
JWKSource<SecurityContext> jwkSource() {
|
|
||||||
return (jwkSelector, context) -> null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,189 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.test.context.runner.ApplicationContextRunner;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
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.settings.AuthorizationServerSettings;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Steve Riesenberg
|
|
||||||
*/
|
|
||||||
public class OAuth2AuthorizationServerPropertiesConfigurationTests {
|
|
||||||
|
|
||||||
private static final String PROPERTIES_PREFIX = "spring.security.oauth2.authorizationserver";
|
|
||||||
|
|
||||||
private static final String CLIENT_PREFIX = PROPERTIES_PREFIX + ".client";
|
|
||||||
|
|
||||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void registeredClientRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestOAuth2AuthorizationServerConfiguration.class)
|
|
||||||
.run((context) -> assertThat(context).doesNotHaveBean(RegisteredClientRepository.class));
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void registeredClientRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
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) -> {
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void registeredClientRepositoryBacksOffWhenRegisteredClientRepositoryBeanPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestRegisteredClientRepositoryConfiguration.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.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");
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void authorizationServerSettingsBeanShouldBeCreatedWhenPropertiesAbsent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestOAuth2AuthorizationServerConfiguration.class)
|
|
||||||
.run((context) -> assertThat(context).hasSingleBean(AuthorizationServerSettings.class));
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void authorizationServerSettingsBeanShouldBeCreatedWhenPropertiesPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestOAuth2AuthorizationServerConfiguration.class)
|
|
||||||
.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");
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void authorizationServerSettingsBacksOffWhenAuthorizationServerSettingsBeanPresent() {
|
|
||||||
// @formatter:off
|
|
||||||
this.contextRunner.withUserConfiguration(TestAuthorizationServerSettingsConfiguration.class,
|
|
||||||
TestOAuth2AuthorizationServerConfiguration.class)
|
|
||||||
.withPropertyValues(PROPERTIES_PREFIX + ".issuer=https://test.com")
|
|
||||||
.run((context) -> {
|
|
||||||
AuthorizationServerSettings settings = context.getBean(AuthorizationServerSettings.class);
|
|
||||||
assertThat(settings.getIssuer()).isEqualTo("https://example.com");
|
|
||||||
});
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Import({ OAuth2AuthorizationServerPropertiesConfiguration.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class TestAuthorizationServerSettingsConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
AuthorizationServerSettings authorizationServerSettings() {
|
|
||||||
return AuthorizationServerSettings.builder().issuer("https://example.com").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue