-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
AuthorizationManager + Method Security Support #9350
Conversation
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, @evgeniycheban! This is a good start.
I've left some feedback inline. In addition to that, please keep classes final
until there is a clear need for extension.
I have some additional thoughts, but I'm guessing they will shake out when you add Pre/PostAuthorize
support.
...ork/security/access/intercept/aopalliance/MethodSecurityAuthorizationManagerInterceptor.java
Outdated
Show resolved
Hide resolved
...ngframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java
Outdated
Show resolved
Hide resolved
2f7ca1f
to
a2ae4a6
Compare
@jzheaux I added support for |
a2ae4a6
to
05a35ee
Compare
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, @evgeniycheban! I've left some additional feedback.
...ccess/expression/method/ExpressionBasedPostMethodInvocationAuthorizationManagerResolver.java
Outdated
Show resolved
Hide resolved
...ccess/expression/method/ExpressionBasedPostMethodInvocationAuthorizationManagerResolver.java
Outdated
Show resolved
Hide resolved
...rg/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java
Outdated
Show resolved
Hide resolved
...ccess/expression/method/ExpressionBasedPostMethodInvocationAuthorizationManagerResolver.java
Outdated
Show resolved
Hide resolved
fb11b02
to
d0244a5
Compare
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, @evgeniycheban! I've left my feedback inline.
* | ||
* @author Evgeniy Cheban | ||
*/ | ||
public interface AfterAuthorizationManager { |
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 like this interface, though I wonder if a better name is AuthorizationManagerAfterAdvice
. The reason for that is that it's clear from the name that it is intercepting and perhaps mutating the return value.
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.
Renamed to AuthorizationManagerAfterAdvice
.
...org/springframework/security/access/method/MethodInvocationAuthorizationManagerResolver.java
Outdated
Show resolved
Hide resolved
d50798a
to
3822746
Compare
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 looks great, @evgeniycheban! It's really starting to take shape. I've left some feedback inline.
AuthorizationManagerBeforeAdvice<MethodInvocation> beforeAdvice, | ||
AuthorizationManagerAfterAdvice<MethodInvocation> afterAdvice) { | ||
this.advice = advice; | ||
this.beforeAdvice = beforeAdvice; |
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 you could instead do:
this.pointcut = Pointcuts.union(this.beforeAdvice, this.afterAdvice);
Then, you can remove the inner class.
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.
Done.
* </p> | ||
* @return the {@link MethodSecurityExpressionHandler} to use | ||
*/ | ||
protected MethodSecurityExpressionHandler methodSecurityExpressionHandler() { |
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 know Spring Security did a lot of this in the past, but I'd like to see if this can be changed to be bean-based instead of based on extending a class.
One way that might work would be to do:
@Autowired(required = false)
public void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler custom) {
this.methodSecurityExpressionHandler = custom;
}
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.
If successful, then let's also make the class final
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.
The @Configuration
class cannot be final because Spring creates a CGLIB proxy for it. I think we could make it just package private.
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.
In fact, setting the proxyBeanMethods
attribute to false
allows the @Configuration
class to be final.
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.
Done
|
||
@Bean | ||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) | ||
AuthorizationManagerAfterAdvice<MethodInvocation> authorizationManagerAfterAdvice() { |
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.
It might not go into this PR, but it would be nice if an application could do:
@Bean
public AuthorizationManagerBeforeAdvice<MethodInvocation> myBeforeAdvice() {
// ....
}
@Bean
public AuthorizationManagerAfterAdvice<MethodInvocation> myAfterAdvice() {
// ...
}
And MethodSecurityConfiguration
would take that one instead of its own.
This would be the equivalent of supplying a custom SecurityMetadataSource
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.
Done.
* | ||
* @author Evgeniy Cheban | ||
*/ | ||
public final class ExpressionBasedAuthorizationManagerAfterAdvice |
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.
While the class is expression-based, it would be ambiguous if other expression-based ways to give advice were added later on to the framework. We might want to call this PreAnnotationAuthorizationManagerAfterAdvice
.
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.
Done.
targetClass); | ||
for (AuthorizationManagerBeforeAdvice<MethodAuthorizationContext> delegate : this.delegates) { | ||
AuthorizationDecision decision = delegate.check(authentication, methodAuthorizationContext); | ||
if (decision != null) { |
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 should match the AffirmativeBased
matcher, since that's the default in @EnableGlobalMethodSecurity
. It returns the first voter that grants and only denies if one of the voters denied.
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.
Done. In the current version the DelegatingAuthorizationManagerBeforeAdvice
returns the first granted AuthorizationDecision
and the denied AuthorizationDecision
only if one of the DelegatingAuthorizationManagerBeforeAdvice
s denied. If multiple DelegatingAuthorizationManagerBeforeAdvice
s denies then the first denied AuthorizationDecision
is returned.
* Creates an instance. | ||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use | ||
*/ | ||
public ExpressionBasedAuthorizationManagerAfterAdvice(MethodSecurityExpressionHandler expressionHandler) { |
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'd recommend that this change to a setter. Typically, Spring Security places required parameters in the constructor and optional ones as setters. Since there's a default handler that will work in most cases, using the default constructor will make it more convenient for applications to use.
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.
Done.
3822746
to
abc9965
Compare
26a3ab8
to
cb62099
Compare
cb62099
to
af92fd5
Compare
@jzheaux I've updated the PR according to your comments. |
a5b27e9
to
2b7e042
Compare
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 your patience, @evgeniycheban, while I got back to you.
I've left some additional feedback inline.
* @param beforeAdvice the {@link AuthorizationManagerBeforeAdvice} to use | ||
* @param afterAdvice the {@link AuthorizationManagerAfterAdvice} to use | ||
*/ | ||
public AuthorizationMethodInterceptor(AuthorizationManagerBeforeAdvice<MethodInvocation> beforeAdvice, |
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.
@rwinch made a good point that it would be nice to see if we can change AuthorizationManagerBeforeAdvice
so that it doesn't extend AuthorizationManager
.
Two ideas come to mind:
- Change this constructor to
public AuthorizationMethodInterceptor(AuthorizationManagerBeforeAdvice<MethodInvocation> beforeAdvice,
AuthorizationManager<MethodInvocation> beforeManager,
AuthorizationManagerAfterAdvice<MethodInvocation> afterAdvice,
AuthorizationManager<MethodInvocation> afterManager)
and then PreAnnotationAuthorizationManagerBeforeAdvice
would split into two classes --PreFilterAuthorizationManagerBeforeAdvice
and PreAuthorizeAuthorizationManager
-- and likewise for PostAnnotationXXX
2. Keep the contract as-is, but split PreAnnotationAuthorizationManagerBeforeAdvice
into PreFilterAuthorizationManagerBeforeAdvice
and something like MethodMatcherAuthorizationManagerBeforeAdvice
. The latter would could have an instance of AuthorizationManager
(e.g. PreAuthorizeAuthorizationManager
) that it would delegate to if the MethodMatcher
matches.
The second seems ideal, but I'm listing both for completeness.
In both cases, it seems like the before advice contract would change to return void
since not all advices would return an AuthorizationDecision
anymore.
1029660
to
60294fe
Compare
806d203
to
1689a72
Compare
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, @evgeniycheban!
I've left some additional feedback inline.
Also, we're probably close enough to add @since 5.5
to new classes.
...rg/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java
Show resolved
Hide resolved
* | ||
* @author Evgeniy Cheban | ||
*/ | ||
public final class MethodSecurityAuthorizationManagerAdvisor extends AbstractPointcutAdvisor { |
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 class is no longer needed. DefaultPointcutAdvisor
should suffice.
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.
Done.
...ngframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java
Show resolved
Hide resolved
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.security.access.expression.method; |
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.
Let's please move all these classes to org.springframework.security.authorization.method
.
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.
Done.
* | ||
* @author Evgeniy Cheban | ||
*/ | ||
public final class DelegatingAuthorizationMethodAfterAdvice |
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'm thinking it would be better to have this use MethodAuthorizationContext
instead of MethodInvocation
. That way, only one MethodAuthorizationContext
needs to be generated in AuthorizationMethodInterceptor
.
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.
Done.
@jzheaux I updated the PR with the JSR-250 security annotations support. |
Thanks again for all the hard work on this PR, @evgeniycheban, it's much appreciated! It's been merged in 20778f7. I also added some polish commits relative to documentation and some small bug fixes. Finally, since we are close to release time, I polished the AOP structure, changing the advice classes to interceptors. These match very nicely with the surrounding interceptor and remove a number of classes needed to support the previous structure. |
Closes gh-9289