Summary

putting an @AuthenticationPrincipal annotation on a Controller parameter, where the parameter's type implements Principal causes the servlet to throw an IllegalArgumentException

Actual Behavior

Basically, since ServletRequestMethodArgumentResolver supports Principal objects, it gets used, and it populates the parameter with an Authentication object, causeing a casting exception when the resolving AuthenticationObject is forced into my UserDetails object

Expected Behavior

AuthenticationPrincipalArgumentResolver should be defaulted to resolve AuthenticationPrincipal arguments, which will do the correct resolution.

The workaround is not having my UserDetails object inherit Principal, or use the actual UserDetails class in the parameter, and then cast it to my subclass, but this is silly. It makes perfect sense that a UserDetails object is also a Princial, and it is in fact retrieved via Authentication Principal

Fix

The fix would be forcing ServletRequestMethodArgumentResolver to be lower priority in argument resolution than AuthenticationPrincipalArgumentResolver

Version

All

Comment From: reliveyy

Found the same issue.

Comment From: anthonyraymond

Still an issue in spring-security:5.3.4

Comment From: sbrannen

See comments in https://github.com/spring-projects/spring-framework/pull/25780#pullrequestreview-490415855

Comment From: eleftherias

Note that to reproduce this issue users must implement Principal and inject their custom class using the @AuthenticationPrincipal annotation.

 class CustomUserDetails implements UserDetails, Principal {
    // ....
@GetMapping("/test")
public String test(@AuthenticationPrincipal CustomUserDetails user) {
    // ...

Comment From: eleftherias

I can confirm that the associated issue https://github.com/spring-projects/spring-framework/pull/25780 fixes this problem.

Since this was fixed in Spring Framework 5.3, it is fixed in all supported versions.