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

Spring Security Command-level Authorization Interceptors #2983

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

bankras
Copy link
Contributor

@bankras bankras commented Feb 20, 2024

Adding interceptors to enable spring-security for command level authorization.

To use these processors in your Axon based application:

  1. register the AuthorizationMessageDispatchInterceptor as an dispatchInterceptor on your CommandGateway.
    This will make sure the principals username and grantedAuthorities are copied from the SecurityContext tot the command messages.
  2. register the CommandAuthorizationInterceptor as an handlerInterceptor on your CommandBus. This will verify
    the authorities on the command, set by @PreAuthorize, with the users granted authorities.

@CLAassistant
Copy link

CLAassistant commented Feb 20, 2024

CLA assistant check
All committers have signed the CLA.

@smcvb smcvb added Type: Feature Use to signal an issue is completely new to the project. Priority 3: Could Low priority. Issues that are nice to have but have a straightforward workaround. Status: In Progress Use to signal this issue is actively worked on. labels Apr 25, 2024
@smcvb smcvb added this to the Release 4.10.0 milestone Apr 25, 2024
@smcvb smcvb requested review from a team, gklijs, CodeDrivenMitch, smcvb and abuijze and removed request for a team April 25, 2024 12:28
@smcvb smcvb changed the title Introducing command level authorization Spring Security Command-level Authorization Interceptors Apr 25, 2024
Copy link
Member

@smcvb smcvb left a comment

Choose a reason for hiding this comment

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

Some remarks about copyright notices, some about JavaDoc, but also some about potential exceptions and making the introduced interceptors more generic.

In all, I think there's value in this PR, However, let's first discuss what's there before merging.

*
* @author Roald Bankras
*/
public class CommandAuthorizationInterceptor implements MessageHandlerInterceptor<CommandMessage<?>> {
Copy link
Member

Choose a reason for hiding this comment

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

I would like to hear your opinion for this interceptor concerning making it more generic by allowing any Message. So, a similar change request as I've placed on the AuthorizationMessageDispatchInterceptor.

If we would make that change, I do think we should rename this class to the MessageAuthorizationInterceptor.
Or, if we want to be extremely specific, the SpringSecurityAuthorizationInterceptor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like it, done it!
There might be need for a QueryInterceptorTest.

@Nonnull
@Override
public CommandMessage<?> handle(@Nonnull CommandMessage<?> message) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Copy link
Member

Choose a reason for hiding this comment

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

Is there a chance any of this might throw a NullPointerException?

Copy link
Contributor Author

@bankras bankras Apr 26, 2024

Choose a reason for hiding this comment

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

getContext() is guaranteed never null. getAuthentication() might return null if no authenticatio information is available

public Object handle(UnitOfWork<? extends CommandMessage<?>> unitOfWork,
@javax.annotation.Nonnull InterceptorChain interceptorChain) throws Exception {
CommandMessage<?> command = unitOfWork.getMessage();
PreAuthorize annotation = command.getPayloadType().getAnnotation(PreAuthorize.class);
Copy link
Member

Choose a reason for hiding this comment

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

I believe the Class#getAnnotation(Class) method returns null if the annotation isn't present.
Perhaps it's good to first check if the annotation is present at all.

You could benefit from the AnnotationUtils within Axon Framework and, for example, use the AnnotationUtils#isAnnotationPresent(AnnotatedElement, Class) operation.

If that returns true, then we can do the validation.
Otherwise, we got a choice to make:

  1. Throw the unauthorized exception since somebody forgot to add the annotation.
  2. Simply allow it, as we don't care.

I'm leaning more towards option 1, as somebody would consciously set this interceptor, I believe...
However, there just might be exceptions to the rule for some users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would lean towards 2. While the interceptor is put there on purpose, not all commands/queries require authorization. So the choice becomes:

2.1. Require PreAuthorize annotation on all messages, but without role or with generic role restriction
2.2. Do not require PreAuthorize annotation for messages that do not require authorization.

@abuijze
Copy link
Member

abuijze commented Apr 29, 2024

Good to see you back here, @bankras

I was wondering if you had considered using a HandlerEnhancerDefinition instead of a MessageHandlerInterceptor? Or maybe there is a place fo both.
Using a HandlerEnhancerDefinition would allow one to annotate the @CommandHandler with @PreAuthorize instead of the payload of the message.

@bankras
Copy link
Contributor Author

bankras commented May 1, 2024

I wasn't aware of the option, but it's an interesting one. Surely beats url-pattern authorization.

Other paths I've been thinking about since Steven's remark:

  • If we use query authorization, do we want support for post filtering?
  • Do we need support to authorize based on aggregate state?
  • Do we need support for an expression language?

"No authorities found"));

log.debug("Authorizing for {} and {}", message.getPayloadType().getName(), annotation.value());
if (userId.contains(new SimpleGrantedAuthority(annotation.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.

This needs to be adjusted, because 'value' can be a SpEL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority 3: Could Low priority. Issues that are nice to have but have a straightforward workaround. Status: In Progress Use to signal this issue is actively worked on. Type: Feature Use to signal an issue is completely new to the project.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants