diff --git a/core/spring-security-core.gradle b/core/spring-security-core.gradle index ee2a79ee99..7a326ed591 100644 --- a/core/spring-security-core.gradle +++ b/core/spring-security-core.gradle @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + import java.util.concurrent.Callable apply plugin: 'io.spring.convention.spring-module' @@ -30,6 +31,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" + testImplementation "org.springframework:spring-core-test" testImplementation "org.springframework:spring-test" testImplementation 'org.skyscreamer:jsonassert' testImplementation 'org.springframework:spring-test' diff --git a/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsAotProcessor.java b/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsAotProcessor.java new file mode 100644 index 0000000000..5036bea2a9 --- /dev/null +++ b/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsAotProcessor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.aot.hint; + +import org.springframework.aot.generate.GenerationContext; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +final class SecurityHintsAotProcessor implements BeanFactoryInitializationAotProcessor { + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + return new AuthorizationProxyFactoryAotContribution(beanFactory); + } + + private static final class AuthorizationProxyFactoryAotContribution + implements BeanFactoryInitializationAotContribution { + + private final ConfigurableListableBeanFactory beanFactory; + + private AuthorizationProxyFactoryAotContribution(ConfigurableListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public void applyTo(GenerationContext context, BeanFactoryInitializationCode code) { + this.beanFactory.getBeanProvider(SecurityHintsRegistrar.class) + .forEach((provider) -> provider.registerHints(context.getRuntimeHints(), this.beanFactory)); + } + + } + +} diff --git a/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsRegistrar.java b/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsRegistrar.java new file mode 100644 index 0000000000..417970bdd4 --- /dev/null +++ b/core/src/main/java/org/springframework/security/aot/hint/SecurityHintsRegistrar.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.aot.hint; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * An interface for registering AOT hints. + * + *
+ * This interface is helpful because it allows for basing hints on Spring Security's + * infrastructural beans like so: + * + *
+ * @Bean
+ * @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ * static SecurityHintsRegistrar proxyThese(AuthorizationProxyFactory proxyFactory) {
+ * return new AuthorizationProxyFactoryHintsRegistrar(proxyFactory, MyClass.class);
+ * }
+ *
+ *
+ * + * The collection of beans that implement {@link SecurityHintsRegistrar} are serially + * invoked by {@link SecurityHintsAotProcessor}, a + * {@link org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor}. + * + *
+ * Since this is used in a + * {@link org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor}, + * the Spring Framework recommendation to only depend on infrastructural beans applies. + * + *
+ * If you do not need Security's infrastructural beans, consider either implementing + * {@link org.springframework.aot.hint.RuntimeHintsRegistrar} or another AOT component as + * indicated in the Spring Framework AOT reference documentation. + * + * @author Josh Cummings + * @since 6.4 + * @see AuthorizeReturnObjectHintsRegistrar + * @see SecurityHintsAotProcessor + */ +public interface SecurityHintsRegistrar { + + /** + * Register hints after preparing them through Security's infrastructural beans + * @param hints the registration target for any AOT hints + * @param beanFactory the bean factory + */ + void registerHints(RuntimeHints hints, ConfigurableListableBeanFactory beanFactory); + +} diff --git a/core/src/main/resources/META-INF/spring/aot.factories b/core/src/main/resources/META-INF/spring/aot.factories index d79bf6b79b..2a24e54073 100644 --- a/core/src/main/resources/META-INF/spring/aot.factories +++ b/core/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,4 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.security.aot.hint.CoreSecurityRuntimeHints +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ +org.springframework.security.aot.hint.SecurityHintsAotProcessor diff --git a/core/src/test/java/org/springframework/security/aot/hint/SecurityHintsAotProcessorTests.java b/core/src/test/java/org/springframework/security/aot/hint/SecurityHintsAotProcessorTests.java new file mode 100644 index 0000000000..d322374f1b --- /dev/null +++ b/core/src/test/java/org/springframework/security/aot/hint/SecurityHintsAotProcessorTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.aot.hint; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.test.generate.TestGenerationContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.context.aot.ApplicationContextAotGenerator; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link SecurityHintsAotProcessor} + */ +public class SecurityHintsAotProcessorTests { + + @Test + void applyToWhenSecurityHintsRegistrarThenInvokes() { + GenerationContext generationContext = new TestGenerationContext(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(AppConfig.class); + ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); + generator.processAheadOfTime(context, generationContext); + verify(context.getBean(SecurityHintsRegistrar.class)).registerHints(any(), any()); + } + + @Configuration + static class AppConfig { + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static SecurityHintsRegistrar hints() { + return mock(SecurityHintsRegistrar.class); + } + + } + +}