Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested bean instance supplier invocation does not retain previous factory method #33180

Closed
ditogam opened this issue Jul 9, 2024 · 6 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@ditogam
Copy link

ditogam commented Jul 9, 2024

Hello,

I have a problem when running native build

here is my build.gradle

./gradlew --version

------------------------------------------------------------
Gradle 8.8
------------------------------------------------------------

Build time:   2024-05-31 21:46:56 UTC
Revision:     4bd1b3d3fc3f31db5a26eecb416a165b8cc36082

Kotlin:       1.9.22
Groovy:       3.0.21
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.3 (Oracle Corporation 21.0.3+7-LTS-jvmci-23.1-b37)
OS:           Linux 6.8.0-36-generic amd64

uname -a
Linux homepc 6.8.0-36-generic spring-projects/spring-boot#36-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun 10 10:49:14 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

cat /etc/os-release

PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

Application code:

package org.example.testsb;

import java.io.Serializable;

import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Service
    public static class PermissionEvaluatorTest implements PermissionEvaluator {

        public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
            return false;
        }

        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
            return false;
        }
    }

    @EnableWebSecurity
    @EnableMethodSecurity(
        securedEnabled = true,
        proxyTargetClass = true
    )
    @RequiredArgsConstructor
    @Configuration
    public static class SecurityConfig {

        @Configuration
        @Order(2)
        @RequiredArgsConstructor
        public static class OAuthSecurityConfig {
            @Bean
            public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher publisher) {
                return new SpringAuthorizationEventPublisher(publisher);
            }

            @Bean
            public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
                DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
                handler.setPermissionEvaluator(permissionEvaluator);

                return handler;
            }

            @Bean
            public AuthorizationManager<MethodInvocation> authorizationManager(
                MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
                PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
                preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);

                return (authentication, object) -> null;
            }

            @Bean
            @Role(ROLE_INFRASTRUCTURE)
            public Advisor authorizationManagerBeforeMethodInterception(AuthorizationManager<MethodInvocation> authorizationManager,
                                                                        AuthorizationEventPublisher publisher) {
                AuthorizationManagerBeforeMethodInterceptor authorizationManagerBeforeMethodInterceptor =
                    AuthorizationManagerBeforeMethodInterceptor.preAuthorize(authorizationManager);
                authorizationManagerBeforeMethodInterceptor.setAuthorizationEventPublisher(publisher);

                return authorizationManagerBeforeMethodInterceptor;
            }
        }
    }
}

I'm compiling using ./gradlew nativeCompile

build/native/nativeCompile/testSB

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodSecurityExpressionHandler': null
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:648)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:636)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1337)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1167)

Full error log error.log

Here are generated aot resources
resources.zip

if I remove implements PermissionEvaluator from PermissionEvaluatorTest, inject it in OAuthSecurityConfig

private final PermissionEvaluatorTest permissionEvaluatorTest;
and create bean


@Bean
        public PermissionEvaluator permissionEvaluator() {
            return new PermissionEvaluator() {
                @Override
                public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
                    return permissionEvaluatorTest.hasPermission(authentication, targetDomainObject, permission);
                }

                @Override
                public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
                    return permissionEvaluatorTest.hasPermission(authentication, targetId, targetType, permission);
                }
            };
        }

then it works as expected

Full working code

package org.example.testsb;

import java.io.Serializable;

import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Service
    public static class PermissionEvaluatorTest {

        public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
            return false;
        }

        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
            return false;
        }
    }

    @EnableWebSecurity
    @EnableMethodSecurity(
        securedEnabled = true,
        proxyTargetClass = true
    )
    @RequiredArgsConstructor
    @Configuration
    public static class SecurityConfig {

        @Configuration
        @Order(2)
        @RequiredArgsConstructor
        public static class OAuthSecurityConfig {
            private final PermissionEvaluatorTest permissionEvaluatorTest;

            @Bean
            public PermissionEvaluator permissionEvaluator() {
                return new PermissionEvaluator() {
                    @Override
                    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
                        return permissionEvaluatorTest.hasPermission(authentication, targetDomainObject, permission);
                    }

                    @Override
                    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
                        return permissionEvaluatorTest.hasPermission(authentication, targetId, targetType, permission);
                    }
                };
            }

            @Bean
            public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher publisher) {
                return new SpringAuthorizationEventPublisher(publisher);
            }

            @Bean
            public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
                DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
                handler.setPermissionEvaluator(permissionEvaluator);

                return handler;
            }

            @Bean
            public AuthorizationManager<MethodInvocation> authorizationManager(
                MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
                PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
                preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);

                return (authentication, object) -> null;
            }

            @Bean
            @Role(ROLE_INFRASTRUCTURE)
            public Advisor authorizationManagerBeforeMethodInterception(AuthorizationManager<MethodInvocation> authorizationManager,
                                                                        AuthorizationEventPublisher publisher) {
                AuthorizationManagerBeforeMethodInterceptor authorizationManagerBeforeMethodInterceptor =
                    AuthorizationManagerBeforeMethodInterceptor.preAuthorize(authorizationManager);
                authorizationManagerBeforeMethodInterceptor.setAuthorizationEventPublisher(publisher);

                return authorizationManagerBeforeMethodInterceptor;
            }
        }
    }
}

It was perfectly worked in '3.2.5', but not works from '3.3.0'

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 9, 2024
@wilkinsona
Copy link
Member

I suspect that this is due to a change in Spring Security or, perhaps, Spring Framework, but I cannot tell for certain based on what you have provided thus far. Unfortunately, the assorted code snippets don't provide the full picture. If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Jul 9, 2024
@ditogam
Copy link
Author

ditogam commented Jul 9, 2024

Hi @wilkinsona
Working branch
Branch that has issue
The same code as in branch with issue, but with springboot 3.2.5 Working with 3.2.5, it starts as expected

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 9, 2024
@wilkinsona
Copy link
Member

Thanks for the sample. The problem also occurs with Spring Boot 3.2.6. It isn't specifically tied to native as an AOT-processed context running on the JVM will also fail in the same way.

With Spring Boot 3.2.6, the problem does not occur if I downgrade Spring Framework from 6.1.8 to 6.1.7, as such this appears to be a Spring Framework regression. We'll transfer this issue to their issue tracker so that they can investigate further.

@wilkinsona wilkinsona removed the status: feedback-provided Feedback has been provided label Jul 9, 2024
@ditogam
Copy link
Author

ditogam commented Jul 9, 2024

Thank you for reply, correct, I couldn't upgrade our codbase version after 3.2.5

@bclozel bclozel transferred this issue from spring-projects/spring-boot Jul 9, 2024
@bclozel bclozel added type: regression A bug that is also a regression in: core Issues in core modules (aop, beans, core, context, expression) labels Jul 9, 2024
@snicoll snicoll removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 9, 2024
@snicoll snicoll added this to the 6.1.11 milestone Jul 9, 2024
@jhoeller jhoeller added the theme: aot An issue related to Ahead-of-time processing label Jul 9, 2024
@wilkinsona
Copy link
Member

wilkinsona commented Jul 10, 2024

Sorry, it working with 6.1.7 vs 6.1.8 wasn't entirely accurate. For that to be the case, you have to declare some of the @Bean methods as static. As presented, the sample only works if you downgrade from 6.1.8 to 6.1.6. While experimenting, I've also noticed that it works with 6.1.8 if all of the app's @Bean methods are static and the dependencies of authorizationManagerBeforeMethodInterception are @Lazy:

        @Configuration
        @Order(2)
        public static class OAuthSecurityConfig {

            @Bean
            public static AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher publisher) {
                return new SpringAuthorizationEventPublisher(publisher);
            }

            @Bean
            public static MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
                DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
                handler.setPermissionEvaluator(permissionEvaluator);

                return handler;
            }

            @Bean
            public static AuthorizationManager<MethodInvocation> authorizationManager(
                MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
                PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
                preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);

                return (authentication, object) -> null;
            }

            @Bean
            @Role(ROLE_INFRASTRUCTURE)
            public static Advisor authorizationManagerBeforeMethodInterception(@Lazy AuthorizationManager<MethodInvocation> authorizationManager,
                                                                        @Lazy AuthorizationEventPublisher publisher) {
                AuthorizationManagerBeforeMethodInterceptor authorizationManagerBeforeMethodInterceptor =
                    AuthorizationManagerBeforeMethodInterceptor.preAuthorize(authorizationManager);
                authorizationManagerBeforeMethodInterceptor.setAuthorizationEventPublisher(publisher);

                return authorizationManagerBeforeMethodInterceptor;
            }
        }

@snicoll snicoll removed the theme: aot An issue related to Ahead-of-time processing label Jul 10, 2024
@snicoll snicoll changed the title AuthorizationManagerBeforeMethodInterceptor problem with Native build Nested bean instance supplier invocation does not retain previous factory method Jul 10, 2024
@snicoll
Copy link
Member

snicoll commented Jul 10, 2024

Thanks to @jhoeller we went to the bottom of this. We're unclear why this scenario worked before but it's a problem with BeanInstanceSupplier that does not restore the previous factory method in case of nested invocations.

@snicoll snicoll added for: backport-to-6.0.x type: bug A general bug and removed type: regression A bug that is also a regression labels Jul 10, 2024
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-6.0.x labels Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

No branches or pull requests

6 participants