From a8c30f79e65b5d98d2879c5268dbc6967057d83a Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Fri, 24 Jun 2022 14:02:15 -0300 Subject: [PATCH] Add Core, MVC and MethodSecurity runtime hints Closes gh-11431 --- .../aot/hint/GlobalMethodSecurityHints.java | 39 ++++ .../resources/META-INF/spring/aot.factories | 2 + .../hint/GlobalMethodSecurityHintsTests.java | 56 ++++++ .../security/aot/hint/CoreSecurityHints.java | 76 ++++++++ .../resources/META-INF/spring/aot.factories | 2 + .../aot/hint/CoreSecurityHintsTests.java | 169 ++++++++++++++++++ .../web/aot/hint/WebMvcSecurityHints.java | 38 ++++ .../resources/META-INF/spring/aot.factories | 2 + .../aot/hint/WebMvcSecurityHintsTests.java | 54 ++++++ 9 files changed, 438 insertions(+) create mode 100644 config/src/main/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHints.java create mode 100644 config/src/main/resources/META-INF/spring/aot.factories create mode 100644 config/src/test/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHintsTests.java create mode 100644 core/src/main/java/org/springframework/security/aot/hint/CoreSecurityHints.java create mode 100644 core/src/main/resources/META-INF/spring/aot.factories create mode 100644 core/src/test/java/org/springframework/security/aot/hint/CoreSecurityHintsTests.java create mode 100644 web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityHints.java create mode 100644 web/src/main/resources/META-INF/spring/aot.factories create mode 100644 web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityHintsTests.java diff --git a/config/src/main/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHints.java b/config/src/main/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHints.java new file mode 100644 index 0000000000..de49586383 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHints.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2022 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.config.aot.hint; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.support.RuntimeHintsUtils; +import org.springframework.core.annotation.SynthesizedAnnotation; +import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; + +/** + * {@link RuntimeHintsRegistrar} for Global Method Security annotations + * + * @author Marcus Da Coregio + * @since 6.0 + */ +class GlobalMethodSecurityHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(EnableGlobalAuthentication.class, RuntimeHintsUtils.ANNOTATION_HINT); + hints.proxies().registerJdkProxy(EnableGlobalAuthentication.class, SynthesizedAnnotation.class); + } + +} diff --git a/config/src/main/resources/META-INF/spring/aot.factories b/config/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..303b362171 --- /dev/null +++ b/config/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.security.config.aot.hint.GlobalMethodSecurityHints diff --git a/config/src/test/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHintsTests.java b/config/src/test/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHintsTests.java new file mode 100644 index 0000000000..06d792cce1 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/aot/hint/GlobalMethodSecurityHintsTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2022 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.config.aot.hint; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsPredicates; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.core.annotation.SynthesizedAnnotation; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GlobalMethodSecurityHints} + * + * @author Marcus Da Coregio + */ +class GlobalMethodSecurityHintsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void setup() { + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories").load(RuntimeHintsRegistrar.class) + .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @Test + void enableGlobalAuthenticationHasHints() { + assertThat(RuntimeHintsPredicates.reflection().onType(EnableGlobalAuthentication.class) + .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints); + assertThat(RuntimeHintsPredicates.proxies().forInterfaces(EnableGlobalAuthentication.class, + SynthesizedAnnotation.class)).accepts(this.hints); + } + +} diff --git a/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityHints.java b/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityHints.java new file mode 100644 index 0000000000..1ee1fbafdb --- /dev/null +++ b/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityHints.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2022 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 java.util.List; +import java.util.stream.Stream; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.security.access.expression.SecurityExpressionOperations; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.ProviderNotFoundException; +import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; +import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * {@link RuntimeHintsRegistrar} for core classes + * + * @author Marcus Da Coregio + * @since 6.0 + */ +class CoreSecurityHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerTypes(getDefaultAuthenticationExceptionEventPublisherTypes(), + (builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + hints.reflection().registerTypes( + List.of(TypeReference.of(SecurityExpressionOperations.class), + TypeReference.of(SecurityExpressionRoot.class)), + (builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS, + MemberCategory.INVOKE_DECLARED_METHODS)); + hints.resources().registerResourceBundle("org.springframework.security.messages"); + } + + private List getDefaultAuthenticationExceptionEventPublisherTypes() { + return Stream.of(AuthenticationFailureBadCredentialsEvent.class, + AuthenticationFailureCredentialsExpiredEvent.class, AuthenticationFailureDisabledEvent.class, + AuthenticationFailureExpiredEvent.class, AuthenticationFailureLockedEvent.class, + AuthenticationFailureProviderNotFoundEvent.class, AuthenticationFailureProxyUntrustedEvent.class, + AuthenticationFailureServiceExceptionEvent.class, AuthenticationServiceException.class, + AccountExpiredException.class, BadCredentialsException.class, CredentialsExpiredException.class, + DisabledException.class, LockedException.class, UsernameNotFoundException.class, + ProviderNotFoundException.class).map(TypeReference::of).toList(); + } + +} diff --git a/core/src/main/resources/META-INF/spring/aot.factories b/core/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..f6c83d8771 --- /dev/null +++ b/core/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.security.aot.hint.CoreSecurityHints diff --git a/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityHintsTests.java b/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityHintsTests.java new file mode 100644 index 0000000000..6421d40a86 --- /dev/null +++ b/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityHintsTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2022 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.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsPredicates; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.security.access.expression.SecurityExpressionOperations; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.ProviderNotFoundException; +import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; +import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CoreSecurityHints} + * + * @author Marcus Da Coregio + */ +class CoreSecurityHintsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void setup() { + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories").load(RuntimeHintsRegistrar.class) + .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @Test + void springSecurityMessagesBundleHasHints() { + assertThat(RuntimeHintsPredicates.resource().forBundle("org.springframework.security.messages")) + .accepts(this.hints); + } + + @Test + void securityExpressionOperationsHasHints() { + assertThat(RuntimeHintsPredicates.reflection().onType(SecurityExpressionOperations.class) + .withMemberCategories(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) + .accepts(this.hints); + } + + @Test + void securityExpressionRootHasHints() { + assertThat(RuntimeHintsPredicates.reflection().onType(SecurityExpressionRoot.class) + .withMemberCategories(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) + .accepts(this.hints); + } + + @Test + void authenticationFailureBadCredentialsEventHasHints() { + assertExceptionEvent(AuthenticationFailureBadCredentialsEvent.class); + } + + @Test + void authenticationFailureCredentialsExpiredEventHasHints() { + assertExceptionEvent(AuthenticationFailureCredentialsExpiredEvent.class); + } + + @Test + void authenticationFailureDisabledEventHasHints() { + assertExceptionEvent(AuthenticationFailureDisabledEvent.class); + } + + @Test + void authenticationFailureExpiredEventHasHints() { + assertExceptionEvent(AuthenticationFailureExpiredEvent.class); + } + + @Test + void authenticationFailureLockedEventHasHints() { + assertExceptionEvent(AuthenticationFailureLockedEvent.class); + } + + @Test + void authenticationFailureProviderNotFoundEventHasHints() { + assertExceptionEvent(AuthenticationFailureProviderNotFoundEvent.class); + } + + @Test + void authenticationFailureProxyUntrustedEventHasHints() { + assertExceptionEvent(AuthenticationFailureProxyUntrustedEvent.class); + } + + @Test + void authenticationFailureServiceExceptionEventHasHints() { + assertExceptionEvent(AuthenticationFailureServiceExceptionEvent.class); + } + + @Test + void authenticationServiceExceptionHasHints() { + assertExceptionEvent(AuthenticationServiceException.class); + } + + @Test + void accountExpiredExceptionHasHints() { + assertExceptionEvent(AccountExpiredException.class); + } + + @Test + void badCredentialsExceptionHasHints() { + assertExceptionEvent(BadCredentialsException.class); + } + + @Test + void credentialsExpiredExceptionHasHints() { + assertExceptionEvent(CredentialsExpiredException.class); + } + + @Test + void disabledExceptionHasHints() { + assertExceptionEvent(DisabledException.class); + } + + @Test + void lockedExceptionHasHints() { + assertExceptionEvent(LockedException.class); + } + + @Test + void usernameNotFoundExceptionHasHints() { + assertExceptionEvent(UsernameNotFoundException.class); + } + + @Test + void providerNotFoundExceptionHasHints() { + assertExceptionEvent(ProviderNotFoundException.class); + } + + private void assertExceptionEvent(Class clazz) { + assertThat(RuntimeHintsPredicates.reflection().onType(clazz) + .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityHints.java b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityHints.java new file mode 100644 index 0000000000..4d104658a0 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityHints.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2022 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.web.aot.hint; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.security.web.access.expression.WebSecurityExpressionRoot; + +/** + * {@link RuntimeHintsRegistrar} for WebMVC classes + * + * @author Marcus Da Coregio + * @since 6.0 + */ +class WebMvcSecurityHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(WebSecurityExpressionRoot.class, (builder) -> builder + .withMembers(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); + } + +} diff --git a/web/src/main/resources/META-INF/spring/aot.factories b/web/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..482b393f7a --- /dev/null +++ b/web/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.security.web.aot.hint.WebMvcSecurityHints diff --git a/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityHintsTests.java b/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityHintsTests.java new file mode 100644 index 0000000000..9543a32bf0 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityHintsTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2022 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.web.aot.hint; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsPredicates; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.security.web.access.expression.WebSecurityExpressionRoot; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebMvcSecurityHints} + * + * @author Marcus Da Coregio + */ +class WebMvcSecurityHintsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void setup() { + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories").load(RuntimeHintsRegistrar.class) + .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @Test + void webSecurityExpressionRootHasHints() { + assertThat(RuntimeHintsPredicates.reflection().onType(WebSecurityExpressionRoot.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)) + .accepts(this.hints); + } + +}