Explorar o código

Add Core, MVC and MethodSecurity runtime hints

Closes gh-11431
Marcus Da Coregio %!s(int64=3) %!d(string=hai) anos
pai
achega
a8c30f79e6

+ 39 - 0
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);
+	}
+
+}

+ 2 - 0
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

+ 56 - 0
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);
+	}
+
+}

+ 76 - 0
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<TypeReference> 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();
+	}
+
+}

+ 2 - 0
core/src/main/resources/META-INF/spring/aot.factories

@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.springframework.security.aot.hint.CoreSecurityHints

+ 169 - 0
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);
+	}
+
+}

+ 38 - 0
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));
+	}
+
+}

+ 2 - 0
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

+ 54 - 0
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);
+	}
+
+}