Taking this working example from the documentation:
@Service
public class MyCustomerService {
@PreAuthorize("hasAuthority('permission:read')")
@PostAuthorize("returnObject.owner == authentication.name")
public Customer readCustomer(String id) { ... }
}
Making it look like this:
public interface MyCustomerServiceDefinition {
@PreAuthorize("hasAuthority('permission:read')")
@PostAuthorize("returnObject.owner == authentication.name")
public Customer readCustomer(String id);
}
@Service
public class MyCustomerService implements MyCustomerServiceDefinition {
@Override
public Customer readCustomer(String id) { ... }
}
It works on standard JVM execution. It does not work with native compilation. The security hooks are not fired (like if there were no annotations at all). Setting the annotations on the implementation makes it work.
Comment From: marcusdacoregio
Hi, @itineric. Thanks for the report.
What version of Spring Security is that problem reproducible? Can you put together a minimal sample that reproduces it?
Comment From: itineric
Here it is: https://github.com/itineric/spring-security-issue-13856
Run the example using standard VM:
> mvn spring-boot:run
And then
> curl localhost:8080
Expected output is an error 500 since the reproduction is minimal and not everything for spring-security implementation is provided.
With native compilation
> mvn -Pnative package
> target/native-hello-world-with-security
And then
> curl localhost:8080
Output is "Hello World!"
When moving the @PreAuthorize from the HelloWorldDefinition interface to the implementation makes it work.
@Override
@RequestMapping("/")
@PreAuthorize("@myAuthenticator.isAuthenticated(authentication)") // we need this here and not on interface, otherwise native compiled exec wont use spring security
public String helloWorld()
{
return "Hello World!";
}
But I need to use interfaces to define these annotations. And I was also expecting things to work the same when running native then when using JVM.
Comment From: itineric
Found a workaround:
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
@Configuration
@ImportRuntimeHints(MyRuntimeHints.class)
public class MyRuntimeHints implements RuntimeHintsRegistrar
{
@Override
public void registerHints(final RuntimeHints hints, final ClassLoader classLoader)
{
hints.reflection().registerType(HelloWorldDefinition.class,
MemberCategory.INTROSPECT_PUBLIC_METHODS);
}
}
Is it a workaround or the solution? Should AOT find this hints by himself when running?
Comment From: marcusdacoregio
Hi, @itineric. The problem is where you placed the annotations, @RequestMapping specifically. Its javadoc says:
NOTE: When using controller interfaces (e.g. for AOP proxying), make sure to consistently put all your mapping annotations - such as @RequestMapping and @SessionAttributes - on the controller interface rather than on the implementation class.
It works if you move the @RequestMapping to the interface instead of the implementation. I have not dug into the details but I guess that it doesn't work because Spring Native does not detect that interface as "reachable" since there is not a single piece of code that calls it directly.
Having said that, I will close this as invalid since I do not think that is a problem with Spring Security. If you want more details you might consider opening an issue in the Spring Boot issue tracker.