tsj
tsj

Reputation: 792

Bind Principal to MyCustomUser class as method argument in controller

I know you can get a username easily in a Spring controller by including Principal as a method argument like:

@GetMapping("/username")
@ResponseBody
public String currentUserName(Principal principal) {
    return principal.getName();
}

But I am ultimately going to want access to members of a MyCustomUser class that I instantiate from a repository with a findBy method. I can put a helper method in the Controller to do the lookup and return the user based on principal.getName(), but can I go a step further and bind to MyCustomUser directly, like

@GetMapping("/stuff")
@ResponseBody
public String stuff(MyCustomUser user) {
    return user.thing();
}

I was looking into creating a converter like (Ref):

@Component
public class PrincipalToMyCustomUserConverter implements Converter<Principal, MyCustomUser> {

    private MyCustomUserRepository userRepository;

    public PrincipalToApplicationUserConverter(MyCustomUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public MyCustomUser convert(Principal source) {
        return this.userRepository.findByUsername(source.getName());
    }

}

But I don't know if that's an appropriate way to grab the repository, and I don't know how to pass the repository when registering the converter (Ref).

Upvotes: 0

Views: 143

Answers (1)

lane.maxwell
lane.maxwell

Reputation: 5862

You're correct in that the converter you're proposing is not appropriate. Your converter can convert from an object of type Principal to an object of type MyCustomUser, however, there is no *Principal* by which to convert. The magic behind the principal injection is that Spring actually gets this from the SecurityContextHolder, it is not deserialized from request...though fields present in the request allow Spring to create the Principal. If you truly want to inject MyCustomUser, use a ModelAttribute. ModelAttributes are available to all of your Spring controller methods.

I generally like to keep stuff like this in it's own class, so I would define a class that held this and other @ControllerAdvice in one place, something like this:

@ControllerAdvice
public class SomeControllerAdvice {
  @Autowired
  private MyCustomUserRepository myCustomUserRepository;

  @ModelAttribute
  public MyCustomUser getUser(Principal principal) {
     return myCustomUserRepository.findByUsername(principal.getName());
  }
}

The above should suffice to make MyCustomUser available to all methods. I would note that you probably want a little error handling here, like skip over if principal is null and whatnot, also have your findByUsername method return an Optional so your can address empty returns.

see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html

Upvotes: 1

Related Questions