-
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
DefaultMethodSecurityExpressionHandler createSecurityExpressionRoot Should Have Protected Access Instead Of Private #12331
Comments
@adase11, thanks for the report. These days, Spring Security tends to prefer encouraging composition, though changing a method to be protected is not out of the question. Can you please describe in more detail what you are trying to do and the result of trying the following to approaches:
|
Thanks @jzheaux - I'm migrating to Spring Security v5.8 from a Spring Boot 2.7.6 project in preparation for Spring Boot 3.0 & Spring Security 6. In the current app I overrode the public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot adminMethodSecurityExpressionRoot = new CustomMethodSecurityExpressionRoot(authentication,
invocation);
adminMethodSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
adminMethodSecurityExpressionRoot.setTrustResolver(new AuthenticationTrustResolverImpl());
adminMethodSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
return adminMethodSecurityExpressionRoot;
}
} Now, because Your first suggestion, implementing |
Just to clarify I think that both of your suggestions would work. I appreciate the recommendations! |
@adase11 Can you please share how did your solution with DefaultMethodSecurityExpressionHandler member variable works? I am facing the same problem, but cannot seem to find a working solution yet. |
@paveljandejsek - I believe that the 2 options that @jzheaux mentioned are the best way to handle this. I chose to go with changing the authorization expressions to refer to bean that contains specialized logic which worked well for my use case. If you require extending Or you could override both @Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation invocation) {
final MethodSecurityExpressionOperations root = createSecurityExpressionRootOverride(authentication);
final MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, invocation, getParameterNameDiscoverer());
ctx.setBeanResolver(getBeanResolver());
return ctx;
}
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
return createSecurityExpressionRootOverride(() -> authentication);
}
private MethodSecurityExpressionOperations createSecurityExpressionRootOverride(Supplier<Authentication> authentication) {
final CustomMethodSecurityExpressionRoot methodSecurityExpressionRoot = new MethodSecurityExpressionRoot(authentication);
methodSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
methodSecurityExpressionRoot.setTrustResolver(authenticationTrustResolver);
methodSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
return methodSecurityExpressionRoot;
} In my opinion - these three options are listed from most to least desirable (where changing the authorization expressions to refer to bean that contains specialized logic is the most desirable). Hope that helps! To extrapolate a bit further on my original proposal - This last solution would be cleaned up significantly if both signatures for public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication, MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot adminMethodSecurityExpressionRoot = new CustomMethodSecurityExpressionRoot(authentication,
invocation);
adminMethodSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
adminMethodSecurityExpressionRoot.setTrustResolver(new AuthenticationTrustResolverImpl());
adminMethodSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
return adminMethodSecurityExpressionRoot;
}
} |
The member variable solution I was working on would look something like: public class CustomMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
implements MethodSecurityExpressionHandler {
private final DefaultMethodSecurityExpressionHandler delegate = new DefaultMethodSecurityExpressionHandler();
@Override
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
return delegate.filter(filterTarget, filterExpression, ctx);
}
...
} I had not yet tested to ensure feature parity |
@adase11 Thank you very much for elaborating! I still hope that private->protected change might happen, since right now it seems it would be the simplest option for migration, but thanks for those suggestions! I might try looking into calling the bean itself as well (altough I want to access some info from the The member variable solution on the other hand seems to lead me to some casting/type checking; while the extension of the Anyway thanks again! |
No problem @paveljandejsek, I agree. |
Another option I believe is to extend the @Component
public class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
CustomMethodSecurityExpressionRoot adminMethodSecurityExpressionRoot =
new CustomMethodSecurityExpressionRoot(authentication, invocation);
adminMethodSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
adminMethodSecurityExpressionRoot.setTrustResolver(new AuthenticationTrustResolverImpl());
adminMethodSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
context.setRootObject(adminMethodSecurityExpressionRoot);
return context;
}
} As @adase11 has already found, using a custom bean in the annotation expression is often preferrable. |
I've added #12356 to provide more detail on the migration steps for Given that, I'll close this ticket. @paveljandejsek, please try the migration guide once that ticket is complete. If the migration steps don't work for you, we can come back here and revisit. |
@jzheaux I agree that usage of custom bean is a more simple solution, but also it makes security expressions less concise. Compare And, if there are more custom methods needs to be used in expressions (combined with The workaround with overriding It will create one But this is OK. What is worse that we still have protected I ended up with this implementation: public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final CustomService customService;
public CustomMethodSecurityExpressionHandler(CustomService customService) {
this.customService = customService;
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
StandardEvaluationContext evaluationContext =
(StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
evaluationContext.setRootObject(createSecurityExpressionRoot(authentication, mi));
return evaluationContext;
}
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation mi) {
return createSecurityExpressionRoot(() -> authentication, mi);
}
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication,
MethodInvocation mi) {
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication, customService);
root.setThis(mi.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
} As you can see, I forced to override both new But what is the canonical way to create your own authorization expression DSL then? Probably, the better way will be to make new Another option will be to remove the old version of I understand your opinion about composition over inheritance and I generally agree with it, but in this case composition doesn't help. I tried, but there are too many internal (not I think the ability to extend Of course, it is alway possible to implement |
Describe the bug
DefaultMethodSecurityExpressionHandler for v5.8.0 adds a new signature for
createSecurityExpressionRoot
ascreateSecurityExpressionRoot(Supplier<Authentication> authentication, MethodInvocation invocation)
in addition to the existingcreateSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation)
. However, the new signature isprivate
while the existing one isprotected
. This causes an issue for any usage that extends theDefaultMethodSecurityExpressionHandler
and overrides the protectedcreateSecurityExpressionRoot
because thecreateEvaluationContext
method always calls the privatecreateSecurityExpressionRoot
, leaving any extension ofDefaultMethodSecurityExpressionHandler
unable to override this behavior. A work around could be to also overridecreateEvaluationContext
however that method usesMethodSecurityEvaluationContext
which is package private and therefore cannot be used when overridingcreateEvaluationContext
.Proposed Fix
Make
MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication, MethodInvocation invocation)
protected instead of privateSample
See - DefaultMethodSecurityExpressionHandler for the code in question
The text was updated successfully, but these errors were encountered: