Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

evgeniycheban
Copy link
Contributor

Closes gh-9289

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 18, 2021
Copy link
Contributor

@jzheaux jzheaux left a 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.

@evgeniycheban
Copy link
Contributor Author

@jzheaux I added support for Pre/PostAuthorize, please take a look. I'm currently covering the code with tests.

Copy link
Contributor

@jzheaux jzheaux left a 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.

@jzheaux jzheaux self-assigned this Jan 26, 2021
@jzheaux jzheaux added in: core An issue in spring-security-core type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 26, 2021
@evgeniycheban evgeniycheban force-pushed the gh-9289 branch 2 times, most recently from fb11b02 to d0244a5 Compare January 27, 2021 22:26
@evgeniycheban evgeniycheban requested a review from jzheaux January 27, 2021 22:36
Copy link
Contributor

@jzheaux jzheaux left a 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 {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to AuthorizationManagerAfterAdvice.

@evgeniycheban evgeniycheban force-pushed the gh-9289 branch 7 times, most recently from d50798a to 3822746 Compare February 4, 2021 11:00
@evgeniycheban evgeniycheban requested a review from jzheaux February 4, 2021 11:09
Copy link
Contributor

@jzheaux jzheaux left a 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;
Copy link
Contributor

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.

Copy link
Contributor Author

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() {
Copy link
Contributor

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;
}

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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() {
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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 DelegatingAuthorizationManagerBeforeAdvices denied. If multiple DelegatingAuthorizationManagerBeforeAdvices denies then the first denied AuthorizationDecision is returned.

* Creates an instance.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public ExpressionBasedAuthorizationManagerAfterAdvice(MethodSecurityExpressionHandler expressionHandler) {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@evgeniycheban
Copy link
Contributor Author

@jzheaux I've updated the PR according to your comments.

Copy link
Contributor

@jzheaux jzheaux left a 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,
Copy link
Contributor

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:

  1. 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.

@evgeniycheban evgeniycheban force-pushed the gh-9289 branch 2 times, most recently from 1029660 to 60294fe Compare March 24, 2021 01:15
@evgeniycheban evgeniycheban requested a review from jzheaux March 24, 2021 01:20
@evgeniycheban evgeniycheban force-pushed the gh-9289 branch 4 times, most recently from 806d203 to 1689a72 Compare March 29, 2021 11:52
Copy link
Contributor

@jzheaux jzheaux left a 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.

*
* @author Evgeniy Cheban
*/
public final class MethodSecurityAuthorizationManagerAdvisor extends AbstractPointcutAdvisor {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* limitations under the License.
*/

package org.springframework.security.access.expression.method;
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@evgeniycheban
Copy link
Contributor Author

@jzheaux I updated the PR with the JSR-250 security annotations support.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 10, 2021

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.

@jzheaux jzheaux closed this Apr 10, 2021
@rwinch rwinch changed the title Consider AuthorizationManager for Method Security AuthorizationManager + Method Security Support Apr 12, 2021
@rwinch rwinch removed this from the 5.5.0-RC1 milestone Apr 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core An issue in spring-security-core type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AuthorizationManager + Method Security Support
4 participants