1
0
mirror of synced 2026-08-04 09:17:02 +00:00

Fail fast when spring-security-access is missing

The "Move Core Access API" refactoring (gh-17847) relocated
MethodSecurityMetadataSourceAdvisor and MethodSecurityInterceptor
from spring-security-core, a mandatory dependency of
spring-security-config, into the new spring-security-access module,
which spring-security-config only depends on optionally.

GlobalMethodSecuritySelector (backing the deprecated
@EnableGlobalMethodSecurity) and ReactiveMethodSecuritySelector
(backing @EnableReactiveMethodSecurity(useAuthorizationManager =
false)) still unconditionally import configuration that constructs
those classes: MethodSecurityMetadataSourceAdvisorRegistrar in proxy
mode, GlobalMethodSecurityConfiguration in both proxy and aspectj
mode, and ReactiveMethodSecurityConfiguration for the legacy reactive
path. Applications that use any of these deprecated configuration
paths without explicitly adding spring-security-access now fail at
startup with a confusing NoClassDefFoundError deep inside Spring's
configuration-processing machinery, instead of an actionable message.

@EnableMethodSecurity and @EnableReactiveMethodSecurity's default
(AuthorizationManager-based) mode, the non-deprecated replacements,
never reference these classes and are unaffected either way.

Add a ClassUtils.isPresent check to both selectors so that, whenever
a legacy configuration path that needs it is chosen (proxy mode,
aspectj mode, or the legacy reactive interceptor), a missing
spring-security-access dependency now fails fast with a clear
IllegalStateException that names the missing dependency and points
to the supported alternative, rather than a NoClassDefFoundError.

This preserves gh-17847's footprint-reduction intent: the check only
runs for the deprecated legacy annotations, so the majority of
applications using @EnableMethodSecurity see no change in behavior or
dependencies. @EnableGlobalMethodSecurity remains deprecated; this
change adds no new investment in it beyond giving existing users of
it a clear diagnostic instead of a confusing crash.

Closes gh-19441

Signed-off-by: jyx-07 <s25069@gsm.hs.kr>
This commit is contained in:
jyx-07
2026-07-20 17:17:59 +09:00
committed by Josh Cummings
parent e4fafce066
commit 7a03cd5a55
3 changed files with 141 additions and 0 deletions
@@ -39,6 +39,8 @@ import org.springframework.util.ClassUtils;
@Deprecated
final class GlobalMethodSecuritySelector implements ImportSelector {
private static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = "org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor";
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
@@ -57,6 +59,16 @@ final class GlobalMethodSecuritySelector implements ImportSelector {
String autoProxyClassName = isProxy ? AutoProxyRegistrar.class.getName()
: GlobalMethodSecurityAspectJAutoProxyRegistrar.class.getName();
boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
if (isProxy || !skipMethodSecurityConfiguration) {
// The proxy-mode advisor registrar and GlobalMethodSecurityConfiguration
// (imported for both proxy and aspectj modes) need types that only exist in
// the optional spring-security-access module.
Assert.state(
ClassUtils.isPresent(METHOD_SECURITY_METADATA_SOURCE_ADVISOR, ClassUtils.getDefaultClassLoader()),
() -> "@EnableGlobalMethodSecurity requires the spring-security-access dependency on the "
+ "classpath. Please add spring-security-access, or migrate to @EnableMethodSecurity "
+ "which does not require it.");
}
List<String> classNames = new ArrayList<>(4);
if (isProxy) {
classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
@@ -26,6 +26,7 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -35,6 +36,8 @@ import org.springframework.util.ClassUtils;
*/
class ReactiveMethodSecuritySelector implements ImportSelector {
private static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = "org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor";
private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
@@ -56,6 +59,11 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
}
else {
Assert.state(
ClassUtils.isPresent(METHOD_SECURITY_METADATA_SOURCE_ADVISOR, ClassUtils.getDefaultClassLoader()),
() -> "@EnableReactiveMethodSecurity(useAuthorizationManager = false) requires the "
+ "spring-security-access dependency on the classpath. Please add spring-security-access, "
+ "or use the default useAuthorizationManager = true which does not require it.");
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
}
if (isDataPresent) {
@@ -0,0 +1,121 @@
/*
* Copyright 2004-present 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.annotation.method.configuration;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.test.support.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for gh-19441: {@code spring-security-access} moved
* {@link org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor}
* out of {@code spring-security-core} and into the optional
* {@code spring-security-access} module. {@link EnableMethodSecurity} and
* {@link EnableReactiveMethodSecurity}'s default (AuthorizationManager-based) mode never
* needed that class and continue to work without {@code spring-security-access} on the
* classpath, but the deprecated legacy method security annotations do need it and
* previously failed with a confusing {@link NoClassDefFoundError} instead of an
* actionable message.
*/
@ClassPathExclusions("spring-security-access-*.jar")
public class Gh19441Tests {
@Test
public void enableMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableReactiveMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableGlobalMethodSecurityWhenProxyModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityProxyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableGlobalMethodSecurityWhenAspectJModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityAspectJConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableReactiveMethodSecurityWhenUseAuthorizationManagerFalseAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityLegacyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Configuration
@EnableMethodSecurity
static class EnableMethodSecurityConfig {
}
@Configuration
@EnableReactiveMethodSecurity
static class EnableReactiveMethodSecurityConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class EnableGlobalMethodSecurityProxyConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ)
static class EnableGlobalMethodSecurityAspectJConfig {
}
@Configuration
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
static class EnableReactiveMethodSecurityLegacyConfig {
}
}