Prevent reflective invocation of private methods in web dispatcher

Signed-off-by: yongjunhong <yongjunh@apache.org>
This commit is contained in:
yongjunhong 2025-08-20 10:48:14 +09:00
parent c38606610c
commit 2eddd3b30a
No known key found for this signature in database
GPG Key ID: 2CB7F4B427C9E5B8
2 changed files with 38 additions and 0 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.web.method.support;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -35,6 +36,7 @@ import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink; import reactor.core.publisher.SynchronousSink;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.core.CoroutinesUtils; import org.springframework.core.CoroutinesUtils;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;
@ -58,6 +60,7 @@ import org.springframework.web.method.HandlerMethod;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Yongjun Hong
* @since 3.1 * @since 3.1
*/ */
public class InvocableHandlerMethod extends HandlerMethod { public class InvocableHandlerMethod extends HandlerMethod {
@ -246,6 +249,16 @@ public class InvocableHandlerMethod extends HandlerMethod {
*/ */
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception { protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod(); Method method = getBridgedMethod();
Object bean = getBean();
if (AopUtils.isCglibProxy(bean) && Modifier.isPrivate(method.getModifiers())) {
throw new IllegalStateException(
"Cannot invoke private method [" + method.getName() + "] on a CGLIB proxy. " +
"Handler methods on proxied components must be public or protected. " +
"Change method visibility or use interface-based JDK proxies if applicable."
);
}
try { try {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) { if (KotlinDetector.isSuspendingFunction(method)) {

View File

@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
@ -42,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThatRuntimeException;
* Tests for {@link InvocableHandlerMethod}. * Tests for {@link InvocableHandlerMethod}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Yongjun Hong
*/ */
class InvocableHandlerMethodTests { class InvocableHandlerMethodTests {
@ -168,6 +170,21 @@ class InvocableHandlerMethodTests {
.withMessageContaining("Illegal argument"); .withMessageContaining("Illegal argument");
} }
@Test
void testPrivateMethodOnCglibProxyThrowsException() throws Exception {
TestController target = new TestController();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.setProxyTargetClass(true);
Object proxy = proxyFactory.getProxy();
Method privateMethod = TestController.class.getDeclaredMethod("privateMethod");
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(proxy, privateMethod);
assertThatIllegalStateException()
.isThrownBy(() -> handlerMethod.invokeForRequest(null, null))
.withMessageContaining("Cannot invoke private method [privateMethod] on a CGLIB proxy");
}
private InvocableHandlerMethod getInvocable(Class<?>... argTypes) { private InvocableHandlerMethod getInvocable(Class<?>... argTypes) {
Method method = ResolvableMethod.on(Handler.class).argTypes(argTypes).resolveMethod(); Method method = ResolvableMethod.on(Handler.class).argTypes(argTypes).resolveMethod();
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new Handler(), method); InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new Handler(), method);
@ -216,4 +233,12 @@ class InvocableHandlerMethodTests {
} }
} }
private static class TestController {
public TestController() {
// Default constructor for proxy creation
}
private void privateMethod() { }
}
} }