-
Notifications
You must be signed in to change notification settings - Fork 6k
Support Mono<Boolean> for Method Security #8584
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
Conversation
@sheiy Please sign the Contributor License Agreement! Click here to manually synchronize the status of this Pull Request. See the FAQ for frequently asked questions. |
@sheiy Thank you for signing the Contributor License Agreement! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @sheiy I've left some comments inline. Can you also put together some tests? Unit tests are good, but we also would want some similar to those in EnableReactiveMethodSecurityTests
@@ -30,4 +31,14 @@ public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) | |||
+ expr.getExpressionString() + "'", e); | |||
} | |||
} | |||
|
|||
public static Mono<Boolean> evaluateAsMonoBoolean(Expression expr, EvaluationContext ctx) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a Reactive return type to a class used by non-reactive applications can potentially break non-reactive applications since depending on the JDK it may load all method definitions as it loads the class. In general, optional dependencies must be placed in distinct classes.
* annotations. | ||
* @return Mono<true> if authorised, Mono<false> otherwise | ||
*/ | ||
Mono<Boolean> before(Authentication authentication, MethodInvocation mi, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally on the reactive side we use Mono<Authentication>
so that if the Authentication
is not needed, it is not subscribed to (i.e. if a method had permitAll
we wouldn't need to load Authentication
from session).
@@ -58,7 +58,7 @@ | |||
* @param preInvocationAdvice the {@link PreInvocationAuthorizationAdvice} to use | |||
* @param postInvocationAdvice the {@link PostInvocationAuthorizationAdvice} to use | |||
*/ | |||
public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, PreInvocationAuthorizationAdvice preInvocationAdvice, PostInvocationAuthorizationAdvice postInvocationAdvice) { | |||
public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, PreInvocationAuthorizationReactiveAdvice preInvocationAdvice, PostInvocationAuthorizationAdvice postInvocationAdvice) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change that we cannot make. We would need to add another constructor
Another note is that we may want to ensure what we do for evaluating SpEL expressions works for #5867 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates. I'm wondering how this fits into #8584 (comment) Is there a way to leverage this to parse SpEL expressions independent of where they come from? I cannot find out how this would be leveraged. I'm also not convinced a direct mapping of imperative to reactive makes sense here. Spring Security's ReactiveAuthorizationManager
supports a richer model (Mono<AuthorizationDecision>
) than the imperative model (true/false).
I'm thinking the first step is to create an ReactiveAuthorizationManager<Expression>
. It should be able to evaluate the Expression
and turn it into Mono<AuthorizationDecision>
. It would support reactive (see ReactiveAdapterRegistry
) or imperative results of Boolean
and AuthorizationDecision
. This could be reused for #5867 I'd create a separate pull request for this.
Then we can create a separate pull request with an implementation of ReactiveAuthorizationManager<MethodInvocation>
that obtains the Expression
and delegates to ReactiveAuthorizationManager<Expression>
.
private ExpressionParser expressionParser = new SpelExpressionParser(); | ||
private BeanResolver br; | ||
private RoleHierarchy roleHierarchy; | ||
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PermissionEvaluator
is not a reactive API, so I don't think we should use it here.
} | ||
|
||
private Mono<Boolean> hasAnyAuthorityName(String prefix, String... roles) { | ||
return getAuthoritySet().map(roleSet -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could be replaced with AuthorityReactiveAuthorizationManager
} | ||
|
||
public final Mono<Boolean> isAuthenticated() { | ||
return isAnonymous().map(aBoolean -> !aBoolean); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could be replaced with AuthenticatedReactiveAuthorizationManager
* @author Sheiy | ||
* @since 5.4 | ||
*/ | ||
public abstract class SecurityExpressionReactiveRoot implements SecurityExpressionReactiveOperations { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that most of the expressions should be mapped to ReactiveAuthorizationManager
implementations. I gave a few examples below. If there isn't one, then we should add it.
* Allows "denyAll" expression | ||
*/ | ||
public final boolean denyAll = false; | ||
private PermissionEvaluator permissionEvaluator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PermissionEvaluator
is not reactive, so we should probably not be using it here.
return Mono.just(true); | ||
} | ||
try { | ||
return Mono.just(ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be a try/catch. Instead I'd expect that ReactiveExpressionUtils
can handle multiple return types using instanceof checks
|
||
private final MethodSecurityMetadataSource attributeSource; | ||
|
||
private final PreInvocationAuthorizationAdvice preInvocationAdvice; | ||
private PreInvocationAuthorizationAdvice preInvocationAdvice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than have another member and and if/else statement, if the user passes in PreInvocationAuthorizationAdvice
it could be adapted to match PreInvocationAuthorizationReactiveAdvice
import org.springframework.expression.Expression; | ||
import reactor.core.publisher.Mono; | ||
|
||
public final class ReactiveExpressionUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used in a single location and can probably be moved to a private method.
* @author Sheiy | ||
* @since 5.1 | ||
*/ | ||
public interface SecurityExpressionReactiveOperations { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use the naming ReactiveSecurityExpressionOperations
* @author Sheiy | ||
* @since 5.4 | ||
*/ | ||
public abstract class SecurityExpressionReactiveRoot implements SecurityExpressionReactiveOperations { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the name should be ReactiveSecurityExpressionRoot
@rwinch Thanks for your reply. I'm sorry for that I'm not familiar with spring-security. |
#4841