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
SecurityEvaluationContextExtensionis 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?