Expected Behavior

Add a ReactiveSecurityEvaluationContextExtension (similar to the existing SecurityEvaluationContextExtension) that provides access to the Authentication object obtained from a Reactor Context, so that it can be used within SpEL expressions of annotated Spring Data queries.

@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
Flux<Message> findInbox();

Context

Spring Data Commons 2.4.0.M2 now contains the required SPI (ReactiveEvaluationContextExtension) to implement reactive SpEL evaluation extensions. (see: DATACMNS-1108)

Comment From: ThomasVitale

@jzheaux can I help with this task?

Comment From: jzheaux

Sure, @ThomasVitale, thanks for the offer!

Since ReactiveEvaluationContextExtension returns a Mono<? extends EvaluationContextExtension>, let's try and reuse SecurityEvaluationContextExtension. I'm thinking something like:

ReactiveSecurityContextHolder.getContext()
    .map(SecurityContext::getAuthentication)
    .map(SecurityEvaluationContextExtension::new)

would work nicely.

@christophstrobl, does getExtensionId need to return something different from SecurityEvaluationContextExtension ("security"), and if so, is there a naming convention that's being followed?

Comment From: antoinechamot

It doesn't work I have "Authentication object cannot be null" with

 @Bean
    ReactiveEvaluationContextExtension securityExtension(){

        return new ReactiveEvaluationContextExtension() {

            @Override
            public String getExtensionId() {
                return "security";
            }

            @Override
            public Mono<? extends EvaluationContextExtension> getExtension() {
                return ReactiveSecurityContextHolder.getContext()
                        .map(SecurityContext::getAuthentication)
                        .map(SecurityEvaluationContextExtension::new);
            }
        };
    }

And @Query("{ $or :[ {'authorId': ?#{principal?.subject}}, {'assigneeId': ?#{principal?.subject}} ] }")

Comment From: jzheaux

@ThomasVitale is this something you are able to add?

Thanks for trying this out @antoinechamot. I can confirm that it works when I modify the MongoDB Spring Data example to include Spring Security and the code you posted. I wonder if your application is using reactive repositories and if the user is authenticated.

Comment From: ThomasVitale

@jzheaux I'm sorry for the late answer. I'll be able to submit a PR by next week. Is it ok to keep "security" as the extension name or should it be something else?

Comment From: jzheaux

Yes, let's leave it as "security" (I tried this myself using the above snippet, and it seemed to work fine). Let's add @christophstrobl as a reviewer of the PR to see if there are any recommended adjustments.

Comment From: ThomasVitale

@jzheaux I tried different things using Spring Data R2DBC as an example, but I always got errors like the following:

EL1008E: Property or field 'authentication' cannot be found on object of type 'java.lang.Object[]' - maybe not public or not valid?

It looks like the extension doesn't get registered since the ReactiveExtensionAwareEvaluationContextProvider is always working on an empty list of extensions.

I have built a small project to demonstrate the error: https://github.com/ThomasVitale/spring-security-data-reactive-example

After running the application, first create a new book:

curl -v -X POST -u isabelle:password -H 'Content-Type: application/json' localhost:8080/books -d '{"name": "The Hobbit"}' Then, try fetching the books created by the currently authenticated user:

curl -v -u isabelle:password localhost:8080/books

Comment From: jzheaux

Hey, @ThomasVitale, please see if this commit helps: https://github.com/jzheaux/spring-data-examples/commit/23b7842d2a8af52de87af9dd3e1f963252ad86d4

This is how I edited the Spring Data MongoDB Example to get it working for me.

Comment From: jzheaux

@ThomasVitale I'm not sure if R2DBC already supports extensions. That's probably a question for the Data team. You might take a look at https://github.com/spring-projects/spring-data-r2dbc/issues/576 which seems related. Spring Data's R2DBC Example may get you started, too.

@antoinechamot another possibility for the behavior you saw is that when SecurityEvaluationContextExtension is registered as a bean in an application context, Spring Data will attempt to use it and fail since there's nothing in SecurityContextHolder.

Comment From: ThomasVitale

@jzheaux thanks for investigating the problem. I reported the issue in the Spring Data R2DBC project: https://github.com/spring-projects/spring-data-r2dbc/issues/658

Should I switch to test the change with Spring Data Mongo and submit a PR, or should I wait for the Spring Data R2DBC fix?

Comment From: svenuthe

It seems that the imperative SecurityEvaluationContextExtension is registered as well and Spring Data cannot distinguish which one it should use. Probably worth its own ticket.

Originally posted by @mp911de in https://github.com/spring-projects/spring-data-r2dbc/issues/658#issuecomment-928934494

According to my and @mp911de findings a second SecurityEvaluationContextExtension is registered by spring-data which prevents the simple use of a ReactiveEvaluationContextExtension bean.

@christophstrobl I fixed this by setting the imperative extension to null:

    @Bean
    fun reactiveSecurityContextExtension(): ReactiveEvaluationContextExtension =
        object : ReactiveEvaluationContextExtension {
            override fun getExtensionId(): String = "reactiveSecurity"

            override fun getExtension(): Mono<out EvaluationContextExtension> =
                ReactiveSecurityContextHolder
                    .getContext()
                    .map { it.authentication as JwtAuthenticationToken }
                    .map { SecurityEvaluationContextExtension(it) }

        }

    @Bean
    fun securityContextExtension(): SecurityEvaluationContextExtension? {
        return null
    }

@marcusdacoregio Will this behavior be changed in the future?

Comment From: jzheaux

@svenuthe, I think Spring Data is willing to take a look if you file a separate issue. I'm not sure there's anything Security can do until that is addressed.

Comment From: ThomasVitale

@jzheaux ASecurityEvaluationContextExtension bean is configured in SecurityDataConfiguration, which is imported in SecurityAutoConfiguration. Should a new issue be opened in Spring Data, in Spring Boot, or would the change be part of this one?

I guess that one option would be adding an extra condition on the SecurityEvaluationContextExtension bean so that it's not created in a reactive application if we have already a ReactiveEvaluationContextExtension bean. Or perhaps it should be Spring Data responsible for choosing the proper bean if both are present?