Expected Behavior

Security SPEL (such as @PreAuthozied) needs custom native-image hints (such as @RegisterReflectionForBinding) when using/referring custom beans by SPEL.

The expectation is to enhance the doc, providing guidelines to consumers of SPEL support within Security.

Current Behavior

Security SPEL (such as @PreAuthozied) works out-of-the-box only for default beans managed by Spring framework.

Context

The problem is reproduced with this simplified snippet run within @jlong’s spring-boot-3-aot project as repro env.

@Configuration
@EnableMethodSecurity(prePostEnabled = true)

// It works in native-image if this line is uncommented
// @RegisterReflectionForBinding(CustomAuthenticationImpl.class)

public class SecurityConfiguration {

    public interface CustomAuthentication extends Authentication {

        boolean isCloudAdmin();
    }

    public static class CustomAuthenticationImpl extends TestingAuthenticationToken implements CustomAuthentication {

        static CustomAuthentication CLOUD_ADMIN = new CustomAuthenticationImpl(true);

        static CustomAuthentication NOT_CLOUD_ADMIN = new CustomAuthenticationImpl(false);

        private final boolean isCloudAdmin;

        private CustomAuthenticationImpl(boolean isCA) {
            super("alex-principle-" + (isCA ? "CA" : "nonCA"), "alex-credentials");
            this.isCloudAdmin = isCA;
        }

        @Override
        public boolean isCloudAdmin() {
            return isCloudAdmin;
        }

    }

    @Service
    public static class MyService {

        public String basicMethod() {
            return "OK";
        }

        // Use method on our CustomAuthentication which seems to need Hint
        @PreAuthorize("authentication.isCloudAdmin()")
        public String isCloudAdminMethod() {
            return "OK";
        }

    }

    @Bean
    ApplicationListener<ApplicationReadyEvent> withSecurity(MyService myService) {
        return event -> {
            SecurityContextHolder.getContext().setAuthentication(CustomAuthenticationImpl.NOT_CLOUD_ADMIN);

            System.out.println("[Security] basicMethod with NOT_CLOUD_ADMIN: " + myService.basicMethod());

            try {
                myService.isCloudAdminMethod();

                System.out.println("[Security] isCloudAdminMethod with NOT_CLOUD_ADMIN: should not happen");
            }
            catch (Exception e) {
                System.out.println("[Security] isCloudAdminMethod with NOT_CLOUD_ADMIN: was secured");
            }

            SecurityContextHolder.getContext().setAuthentication(CustomAuthenticationImpl.CLOUD_ADMIN);

            System.out.println("[Security] isCloudAdminMethod with CLOUD_ADMIN: " + myService.isCloudAdminMethod());
        };
    }

}

which works on JRE and fails on native-image with:

java.lang.IllegalArgumentException: Failed to evaluate expression 'authentication.isCloudAdmin()'
        at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:33) ~[na:na]
        at org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager.check(PreAuthorizeAuthorizationManager.java:68) ~[na:na]
at Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method isCloudAdmin() cannot be found on type com.example.aot.security.SecurityConfiguration$CustomAuthenticationImpl
        at org.springframework.expression.spel.ast.MethodReference.findAccessorForMethod(MethodReference.java:225) ~[na:na]

Here's link to the slack channel discussion.