-
Notifications
You must be signed in to change notification settings - Fork 6k
Make PrePostAdviceReactiveMethodInterceptor more flexible in how it detects supported methods #9650
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
Comments
Can you please demonstrate to me how this would work? |
It is entirely case-specific and relies on the capabilities of the framework in question. But this is how I implemented it for GraphQL:
Mono.deferContextual(ctx -> Mono.fromFuture(graphQL.executeAsync(buildInput(query, variables, ctx); Note how Reactor's subscriber context is used as GraphQL context (in
//dataFetchingEnvironment is provided to all resolvers by the GraphQL engine
return Mono.fromSupplier(() -> proceedWithReflectiveInvocation())
.contextWrite(dataFetchingEnvironment.getContext()) //restores the subscriber context
.toFuture(); //because graphql-java only works with CompletableFuture and isn't Reactor aware And apart from a one-liner hack I made in @PreAuthorize("hasRole('ADMIN')")
@GraphQLQuery //Meaning this method will be called by the GraphQL engine (triggering the wrapping code from above)
public Project project(String id) {
...
} I only had to trick with //Ignore the real return type on GraphQL methods, and pretend it's Mono
Class<?> returnType = method.isAnnotationPresent(GraphQLQuery.class) ? Mono.class : method.getReturnType(); This is of course a clumsy proof of concept, not a real implementation but, strictly speaking, my use-case would be fully satisfied by merely replacing Alternative implementationI also have an alternative implementation where I trigger Class<?> returnType = invocation instancof ReactiveWrappedInvocation
? ((ReactiveWrappedInvocation) invocation).getReturnType();
: method.getReturnType(); In this implementation, Fair warningI experimented quite a lot with different implementations, so I might have mixed something up in my examples. I'll push a fully working example somewhere if you're interested (including the hacked version of ConclusionAs you can see, this whole thing depends on the framework having its own context propagation and being the one invoking (at least) the non-reactive protected methods. So it can't be Spring invoking them like it usually would. Now, I completely understand this is an exotic scenario, but I find it common enough (as I mentioned, I've seen workflow engines having something similar, GraphQL too...) and the changes needed in Spring Security minuscule enough (and potentially useful for Spring's own internal extensibility), that it's easily worth it. |
The biggest problem is that we need to ensure that there is the glue code that allows stitching the contexts together. The example you have provided does demonstrate a way to do this, but is not fully baked. For example, how do we know the type is a It may be of interest, but we are working on GraphQL + Spring (which will include Spring Security support) in https://github.com/spring-projects-experimental/spring-graphql |
Not sure I'm following? If it's already a Would you be open to making a trivial addition to the interceptor, something along the lines of: public Class<?> getReturnType(Method method) {
return method.getReturnType();
} ? This would already open up a world of possibilities... |
Class<?> returnType = method.isAnnotationPresent(GraphQLQuery.class) ? Mono.class : method.getReturnType(); Why does
We cannot just open it up without some sort of API that ensures the user knows what they are doing. Out of the box the code only supports Reactor return types, so we cannot just allow anything to go through. We'd need a strategy or something.
I'd be interested in seeing the fully working example |
(I now realize I've edited my previous response before seeing your last message)
Well... it's a proof of concept, that's exactly what it was supposed to do 😅 I opened this issue to discuss what a fully baked solution would look like.
Was just proving a point, of course a real implementation wouldn't have such hard-coded expectations. (In my case,
Yes, of course, this is exactly what I want to discuss. To see if you'd be open to something like that, how would you go about implementing such a thing etc... I'll try coming up with a proper implementation, so we have something more concrete to discuss. |
Oh, and thanks for pointing me to the experimental starter! I wasn't aware of its existence. While I'm very fond of my own GraphQL SPQR (also my project) based starter, I might be able to use it to learn how to better integrate with certain Spring features, like Spring Security! |
We (the Spring and graphql teams) are still experimenting with the best way to handle the context. The first step is allowing users to return |
I see. In SPQR starter, I handled that the way I described above (no changes in |
I added a sample application that takes advantage of the newly added ReactorDataFetcherAdapter support in spring-graphql. The SalaryService is protected using The preferred way to support other scenarios is to adapt to ReactorContext externally rather than within the method support. This demonstrates that this is possible for GraphQL java support and so I don't think this ticket is necessary. Does this resolve your specific concrete scenario? If so, I think we should close this issue. |
(Closed the issue by accident) As for this issue, it was really for the step beyond: protecting even the non-publisher returning methods. If in the The problem is that the distinction would have to be made per invocation... GraphQL invocations should be exempted (because only GraphQL invocations would be adapted) while other invocations not... And that seems to not be solvable generically... So I guess we can close this issue and I'll have to stick to a custom implementation to trigger the interceptor for GraphQL only. I'm almost done with this, so I'll post an example soon. |
Closing as duplicate of gh-9401 |
Expected Behavior
PrePostAdviceReactiveMethodInterceptor
should enable the user to customize how the supported methods are detected.Current Behavior
The current logic checks whether the intercepted method returns a
Publisher
and, as of a couple of days ago, whether it is a Kotlin suspending function or returns Kotlin'sFlow
. It enables no customization by the user.Context
Enabling a degree of extensibility to this logic could make Spring Security usable in more scenarios.
More supported kinds of methods could be added by framework/language/library authors as a part of their own Spring integration. Examples might include libraries (like Mutiny, which doesn't comply with the reactive streams spec), or languages (e.g. Scala or Groovy specific types, akin to Kotlin's
Flow
). An even more interesting example are workflow orchestration/BPMN engines, that invoke configured methods (usually reflectively) and commonly have their own interceptors and a shared context available around every invocation. This context can act as an alternative side-channel by which the Reactor context can be propagated even when the reactive chain is interrupted. This means that in such environments any method could potentially be wrapped in e.g.Mono
with the Reactor context fully preserved. This exact scenario is the one I'm facing. I'm developing a Spring Boot starter around graphql-java, which (like all GraphQL environments) provides a shared context available around each method invocation. This enables me to store Reactor's context inside the GraphQL context, wrap any method invocation into aMono
and re-attach the Reactor context. So despite graphql-java having no support for reactive streams and working only withCompletableFutures
, Spring's reactive security keeps working just fine as the chain of reactive calls remains effectively unbroken. I have a limited yet fully working implementation, but it requires the underlying method to return aPublisher
for no other reason than to satisfy the checks inPrePostAdviceReactiveMethodInterceptor
. If these could be extended somehow, any method would be supported. My current implementation provides no way for the wrapped methods to enrich/replace the Reactor context, but that is entirely possible too, with all the semantics properly preserved (like only downstream methods having the new context etc).Additionally, Spring Security could itself use this extensibility to cleanly register more supported method types as they get added in the future. The current list consisting of
Publisher
-returning and Kotlin-specific methods is unlikely to stay fixed forever, andPrePostAdviceReactiveMethodInterceptor
is already complicated enough with a few nestedif
s containing entirely divergent logic. E.g.Flow
-handling logic shares virtually nothing with theMono
-handling one.Possible approaches
PrePostAdviceReactiveMethodInterceptor
could delegate the check if the method is supported to an injectable strategy, that would also be an adapter to/fromPublisher
(like howFlow
is currently adapted). Perhaps hooking intoReactiveAdapterRegistry
more formally is the way to go...Alternatively, maybe a special subtype of
MethodInvocation
, e.g.ReactiveAdaptedInvocation
that itself acts as the strategy/adapter could be used.I'd happily undertake this work if there is interest, but I'd really need some discussion on the approach first.
The text was updated successfully, but these errors were encountered: