Skip to content

Resolve Principal argument only when not annotated #25780

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

Closed
wants to merge 4 commits into from

Conversation

anthonyraymond
Copy link
Contributor

@anthonyraymond anthonyraymond commented Sep 16, 2020

ServletRequestMethodArgumentResolver happend early in the ArgumentResolver chain. One of his ability is to resolve Principal, that's great and it works well.

But when a parameter of type Principal is annotated we don't want to get the Principal from the HttpServletRequest.getUserPrincipal().
This feature is even in conflict with the spring-security documentation and the @AuthenticationPrincipal annotation which is supposed to resolve the Principal from Authentication.getPrincipal().

Having the ServletRequestMethodArgumentResolver resolving annotated Principal makes the @AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver useless and missleading.

Fix spring-projects/spring-security#4151

ServletRequestMethodArgumentResolver happend early in the `ArgumentResolver` chain. One of this ability is to resolve `Principal`, that's great and it works well.

But most of the time when a parametre of type `Principal` is annotated we don't want to get the `Principal` from the `HttpServletRequest.getUserPrincipal()`.
This feature is actually conflicting with the **spring-security** documentation and the `@AuthenticationPrincipal` annotation which is supposed to resolve the Principal from Authentication.getPrincipal().

Fix #4151
@pivotal-issuemaster
Copy link

@anthonyraymond Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

@pivotal-issuemaster
Copy link

@anthonyraymond Thank you for signing the Contributor License Agreement!

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 16, 2020
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Sep 16, 2020
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
(Principal.class.isAssignableFrom(paramType) && parameterAnnotations.length == 0) ||
Copy link
Member

Choose a reason for hiding this comment

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

Merely checking for the absence of an annotation would not be sufficient, since the parameter could be annotated with annotations other than Spring Security's @AuthenticationPrincipal.

Copy link
Member

@sbrannen sbrannen Sep 17, 2020

Choose a reason for hiding this comment

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

I think the Spring Security issue would need to be addressed within Spring Security, potentially making use of RequestMappingHandlerAdapter.setArgumentResolvers(List<HandlerMethodArgumentResolver>) (as opposed to setCustomArgumentResolvers(...)).

@rstoyanchev and @rwinch, thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think setting the default resolvers is a good way to go.

We have some precedent with arguments of type Map where we do check that it isn't annotated in some way, see 5a3ff35. We could so something similar here given that Principal is also a very high level interface with many possible uses and implementations. What I'm wondering though is why this problem surfaces only now, was there some change in Spring Security or did it never work?

Copy link
Contributor

@rstoyanchev rstoyanchev Sep 21, 2020

Choose a reason for hiding this comment

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

Or does it depend on the user Object of the application?

Under spring-projects/spring-security#4151 it says one option is for UserDetails to not inherit Principal but from what I see UserDetails doesn't .

Copy link
Member

Choose a reason for hiding this comment

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

We have some precedent with arguments of type Map where we do check that it isn't annotated in some way, see 5a3ff35.

OK. I wasn't aware of that.

Copy link
Member

Choose a reason for hiding this comment

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

Or does it depend on the user Object of the application?

Yes, it depends on the user-specific implementation of UserDetails.

Under spring-projects/spring-security#4151 it says one option is for UserDetails to not inherit Principal but from what I see UserDetails doesn't .

Right. The UserDetails interface does not extend Principal by default, though a developer may of course choose to implement Principal if they want (or need) to.

I have implemented in UserDetails in several applications of the years, and I have never had it implement Principal as well. I also have never seen anyone do that. But... as I mentioned above, it's certainly an option if it makes sense in a given application.

Copy link
Contributor Author

@anthonyraymond anthonyraymond Sep 22, 2020

Choose a reason for hiding this comment

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

Hi guys thanks for your investment in this issue.

to answer @rstoyanchev

What I'm wondering though is why this problem surfaces only now, was there some change in Spring Security or did it never work?

My guess is that it's never worked, people use the @AuthenticationPrincipal and they get what they asks for: a Principal. It's not the expected one, because it's not coming from the right place, but it's a principal and it's good enough after 3 hours of tweaking the Auth process to get what you want🤝. All Most peoples HATES having to deal with auth 😨, and if the framework is handling this good enough that's a deal 😄 .

But this behavior has always been a problem:

  • 2019 SO post: HttpServletRequest.getUserPrincipal() contains the Authentication (which extends Principal) so it returns it, and it does not extract the Principal from the Authentication. There are multiple solutions for this around SO and most of them say : Use org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken in place of KeycloakPrincipal (which is an implementation of Authentication) and magically it starts to work, but not for the good reasons.
  • 2015 SO post: Because HttpServletRequest.getUserPrincipal() contains the Authentication (which extends Principal).

Basically what you can get with the Google search "Current user principal is not of type" is related to this issue.

Both of these issues come from the fact that the Principal resolved is from HttpServletRequest.getUserPrincipal() instead of SecurityContext.getAuthentication.getPrincipal().

The nasty thing about that is that the @AuthenticationPrincipal has not only become not relevant but also missleading, because all class extending Principal can be injected into a Principal type (thanks to the spring-framework, the annotation takes no part in this), but as soon as you want to get your implementation of Principal you are doomed.


A side question that i can't figure out on my own (no sarcasm, that's a real question):

  • Why is the spring-framework trying to inject a Principal ? Isn't java.security.Principal informaly but well accepted as an authentication class? IMHO it makes more sense to delegate that behaviour to the security part of spring.

Copy link

@imjmakki imjmakki left a comment

Choose a reason for hiding this comment

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

great job

@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 22, 2020
@rstoyanchev rstoyanchev self-assigned this Sep 22, 2020
@rstoyanchev rstoyanchev added this to the 5.3 RC2 milestone Sep 22, 2020
@rstoyanchev
Copy link
Contributor

Thanks for the extra detail. This is scheduled for 5.3 now.

Why is the spring-framework trying to inject a Principal ?

It is one of many request properties that ServletRequestMethodArgumentResolver aims to expose.

@rstoyanchev rstoyanchev changed the title Disable ArgumentResolver for annotated Principal Resolve Principal argument only when not annotated Sep 22, 2020
@anthonyraymond
Copy link
Contributor Author

Thanks for the quick reply.

Since this will most likely get merged, i added a test to ensure it wont break in the future.

Copy link

@imjmakki imjmakki left a comment

Choose a reason for hiding this comment

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

nice

rstoyanchev added a commit that referenced this pull request Sep 25, 2020
Copy link

@imjmakki imjmakki left a comment

Choose a reason for hiding this comment

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

good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AuthenticationPrincipalArgumentResolver not used when argument class implements Principal
6 participants