How to get active user's UserDetails
Asked Answered
P

9

174

In my controllers, when I need the active (logged in) user, I am doing the following to get my UserDetails implementation:

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

It works fine, but I would think Spring could make life easier in a case like this. Is there a way to have the UserDetails autowired into either the controller or the method?

For example, something like:

public ModelAndView someRequestHandler(Principal principal) { ... }

But instead of getting the UsernamePasswordAuthenticationToken, I get a UserDetails instead?

I'm looking for an elegant solution. Any ideas?

Presumptive answered 6/1, 2012 at 21:7 Comment(0)
H
232

Preamble: Since Spring-Security 3.2 there is a nice annotation @AuthenticationPrincipal described at the end of this answer. This is the best way to go when you use Spring-Security >= 3.2.

When you:

  • use an older version of Spring-Security,
  • need to load your custom User Object from the Database by some information (like the login or id) stored in the principal or
  • want to learn how a HandlerMethodArgumentResolver or WebArgumentResolver can solve this in an elegant way, or just want to an learn the background behind @AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver (because it is based on a HandlerMethodArgumentResolver)

then keep on reading — else just use @AuthenticationPrincipal and thank to Rob Winch (Author of @AuthenticationPrincipal) and Lukas Schmelzeisen (for his answer).

(BTW: My answer is a bit older (January 2012), so it was Lukas Schmelzeisen that come up as the first one with the @AuthenticationPrincipal annotation solution base on Spring Security 3.2.)


Then you can use in your controller

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

That is ok if you need it once. But if you need it several times its ugly because it pollutes your controller with infrastructure details, that normally should be hidden by the framework.

So what you may really want is to have a controller like this:

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

Therefore you only need to implement a WebArgumentResolver. It has a method

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

That gets the web request (second parameter) and must return the User if its feels responsible for the method argument (the first parameter).

Since Spring 3.1 there is a new concept called HandlerMethodArgumentResolver. If you use Spring 3.1+ then you should use it. (It is described in the next section of this answer))

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

You need to define the Custom Annotation -- You can skip it if every instance of User should always be taken from the security context, but is never a command object.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

In the configuration you only need to add this:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@See: Learn to customize Spring MVC @Controller method arguments

It should be noted that if you're using Spring 3.1, they recommend HandlerMethodArgumentResolver over WebArgumentResolver. - see comment by Jay


The same with HandlerMethodArgumentResolver for Spring 3.1+

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

In the configuration, you need to add this

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@See Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface


Spring-Security 3.2 Solution

Spring Security 3.2 (do not confuse with Spring 3.2) has own build in solution: @AuthenticationPrincipal (org.springframework.security.web.bind.annotation.AuthenticationPrincipal) . This is nicely described in Lukas Schmelzeisen`s answer

It is just writing

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

To get this working you need to register the AuthenticationPrincipalArgumentResolver (org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) : either by "activating" @EnableWebMvcSecurity or by registering this bean within mvc:argument-resolvers - the same way I described it with may Spring 3.1 solution above.

@See Spring Security 3.2 Reference, Chapter 11.2. @AuthenticationPrincipal


Spring-Security 4.0 Solution

It works like the Spring 3.2 solution, but in Spring 4.0 the @AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver was "moved" to an other package:

(But the old classes in its old packges still exists, so do not mix them!)

It is just writing

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

To get this working you need to register the (org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver : either by "activating" @EnableWebMvcSecurity or by registering this bean within mvc:argument-resolvers - the same way I described it with may Spring 3.1 solution above.

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@See Spring Security 5.0 Reference, Chapter 39.3 @AuthenticationPrincipal

Hannie answered 7/1, 2012 at 12:23 Comment(7)
Alternatively just create a bean that has a getUserDetails() method and @Autowire that into your controller.Magallanes
Implementing this solution, I set a breakpoint at the top of the resolveArgument() but my app never steps into the web argument resolver. Your spring configuration in the servlet context not the root context, right?Pylle
@Jay: This configuration is part of the root context, not servlet context. -- it seams that I have forget to specify the id (id="applicationConversionService") in the exampleHannie
It should be noted that if you're using Spring 3.1, they recommend HandlerMethodArgumentResolver over WebArgumentResolver. I got HandlerMethodArgumentResolver to work by configuringit with <annotation-driven> in the servlet context. Other than that, I implemented the answer as posted here and everything works greatPylle
@Magallanes Why not just create static method?Basal
AuthenticationPrincipalArgumentResolver is now deprecatedCooperate
@IgorDonin: Thanks for the hint, I added a Section for Spring >4.0Hannie
M
67

While Ralphs Answer provides an elegant solution, with Spring Security 3.2 you no longer need to implement your own ArgumentResolver.

If you have a UserDetails implementation CustomUser, you can just do this:

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

See Spring Security Documentation: @AuthenticationPrincipal

Mandle answered 4/4, 2014 at 8:28 Comment(4)
for those who don't like to read provided links, this has to be enabled either by @EnableWebMvcSecurity or in XML: <mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>Grochow
How to check if the customUser is null or not?Semela
if (customUser != null) { ... }Billow
How to mock this annotated parameter using mockito?Andreandrea
C
27

Spring Security is intended to work with other non-Spring frameworks, hence it is not tightly integrated with Spring MVC. Spring Security returns the Authentication object from the HttpServletRequest.getUserPrincipal() method by default so that's what you get as the principal. You can obtain your UserDetails object directly from this by using

UserDetails ud = ((Authentication)principal).getPrincipal()

Note also that the object types may vary depending on the authentication mechanism used (you may not get a UsernamePasswordAuthenticationToken, for example) and the Authentication doesn't strictly have to contain a UserDetails. It can be a string or any other type.

If you don't want to call SecurityContextHolder directly, the most elegant approach (which I would follow) is to inject your own custom security context accessor interface which is customized to match your needs and user object types. Create an interface, with the relevant methods, for example:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

You can then implement this by accessing the SecurityContextHolder in your standard implementation, thus decoupling your code from Spring Security entirely. Then inject this into the controllers which need access to security information or information on the current user.

The other main benefit is that it is easy to make simple implementations with fixed data for testing, without having to worry about populating thread-locals and so on.

Chromatics answered 6/1, 2012 at 22:58 Comment(4)
I was considering this approach, but I wasn't sure a) how exactly to do it the Right Way(tm) and b) if there would be any threading issues. Are you certain there would be no issues there? I'm going with the annotation method posted above, but I think this is still a decent way of going about it. Thanks for posting. :)Presumptive
It is technically the same as accessing the SecurityContextHolder directly from your controller, so there shouldn't be any threading issues. It just keeps the call to a single place and allows you to easily inject alternatives for testing. You can also re-use the same approach in other non-web classes which need access to security information.Chromatics
Gotcha... that's what I was thinking. This will be a good one to come back to if I have issues unit testing with the annotation method, or if I want to decrease coupling with Spring.Presumptive
@LukeTaylor can you please further explain this approach, I'm a bit new to spring and spring security, so I don't quite get how to implement this. Where do I implement this so it would be accessed? to my UserServiceImpl?Dowzall
S
9

Implement the HandlerInterceptor interface, and then inject the UserDetails into each request that has a Model, as follows:

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}
Sarchet answered 9/1, 2012 at 15:2 Comment(2)
Thanks @atrain, that's useful and elegant. Also, I had to add the <mvc:interceptors> in my application configuration file.Camacho
It is better get authorized user in template via spring-security-taglibs: https://mcmap.net/q/101572/-how-to-get-active-user-39-s-userdetailsGriner
L
9

Starting with Spring Security version 3.2, the custom functionality that has been implemented by some of the older answers, exists out of the box in the form of the @AuthenticationPrincipal annotation that is backed by AuthenticationPrincipalArgumentResolver.

An simple example of it's use is:

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

CustomUser needs to be assignable from authentication.getPrincipal()

Here are the corresponding Javadocs of AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver

Lactic answered 20/5, 2014 at 6:42 Comment(2)
@nbro When I added the version specific solution, none of the other solutions had been updated to take into account this solutionLactic
AuthenticationPrincipalArgumentResolver is now deprecatedCooperate
T
6
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
Tetramethyldiarsine answered 19/2, 2014 at 13:52 Comment(0)
G
0

And if you need authorized user in templates (e.g. JSP) use

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

together with

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
Griner answered 5/6, 2017 at 16:22 Comment(0)
I
0

You can try this: By Using Authentication Object from Spring we can get User details from it in the controller method . Below is the example , by passing Authentication object in the controller method along with argument.Once user is authenticated the details are populated in the Authentication Object.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}
Infringement answered 26/9, 2019 at 20:48 Comment(0)
C
0

To get the Active Users Details you can use @AuthenticationPrincipal in your controller like this:-

public String function(@AuthenticationPrincipal UserDetailsImpl user,
                    Model model){   
    model.addAttribute("username",user.getName()); //this user object 
contains user details 
    return "";
}

UserDetailsImpl.java

import com.zoom.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
public class UserDetailsImpl implements UserDetails {

@Autowired
private User user;

public UserDetailsImpl(User user) {
    this.user = user;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ADMIN");
    return List.of(simpleGrantedAuthority);
}

@Override
public String getPassword() {
    return user.getPassword();
}

@Override
public String getUsername() {
    return user.getEmail();
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    return true;
}

public String getRole(){
    return user.getRole();
}

public String getName(){
    return user.getUsername();
}

public User getUser(){
    return user;
}
}
Conjunctive answered 26/10, 2021 at 3:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.