java - When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?

ID : 10357

viewed : 34

Tags : javaspringspring-mvcspring-securityjava

Top 5 Answer for java - When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?

vote vote

91

If you are using Spring 3, the easiest way is:

 @RequestMapping(method = RequestMethod.GET)     public ModelAndView showResults(final HttpServletRequest request, Principal principal) {       final String currentUser = principal.getName();   } 
vote vote

83

A lot has changed in the Spring world since this question was answered. Spring has simplified getting the current user in a controller. For other beans, Spring has adopted the suggestions of the author and simplified the injection of 'SecurityContextHolder'. More details are in the comments.


This is the solution I've ended up going with. Instead of using SecurityContextHolder in my controller, I want to inject something which uses SecurityContextHolder under the hood but abstracts away that singleton-like class from my code. I've found no way to do this other than rolling my own interface, like so:

public interface SecurityContextFacade {    SecurityContext getContext();    void setContext(SecurityContext securityContext);  } 

Now, my controller (or whatever POJO) would look like this:

public class FooController {    private final SecurityContextFacade securityContextFacade;    public FooController(SecurityContextFacade securityContextFacade) {     this.securityContextFacade = securityContextFacade;   }    public void doSomething(){     SecurityContext context = securityContextFacade.getContext();     // do something w/ context   }  } 

And, because of the interface being a point of decoupling, unit testing is straightforward. In this example I use Mockito:

public class FooControllerTest {    private FooController controller;   private SecurityContextFacade mockSecurityContextFacade;   private SecurityContext mockSecurityContext;    @Before   public void setUp() throws Exception {     mockSecurityContextFacade = mock(SecurityContextFacade.class);     mockSecurityContext = mock(SecurityContext.class);     stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);     controller = new FooController(mockSecurityContextFacade);   }    @Test   public void testDoSomething() {     controller.doSomething();     verify(mockSecurityContextFacade).getContext();   }  } 

The default implementation of the interface looks like this:

public class SecurityContextHolderFacade implements SecurityContextFacade {    public SecurityContext getContext() {     return SecurityContextHolder.getContext();   }    public void setContext(SecurityContext securityContext) {     SecurityContextHolder.setContext(securityContext);   }  } 

And, finally, the production Spring config looks like this:

<bean id="myController" class="com.foo.FooController">      ...   <constructor-arg index="1">     <bean class="com.foo.SecurityContextHolderFacade">   </constructor-arg> </bean> 

It seems more than a little silly that Spring, a dependency injection container of all things, has not supplied a way to inject something similar. I understand SecurityContextHolder was inherited from acegi, but still. The thing is, they're so close - if only SecurityContextHolder had a getter to get the underlying SecurityContextHolderStrategy instance (which is an interface), you could inject that. In fact, I even opened a Jira issue to that effect.

One last thing - I've just substantially changed the answer I had here before. Check the history if you're curious but, as a coworker pointed out to me, my previous answer would not work in a multi-threaded environment. The underlying SecurityContextHolderStrategy used by SecurityContextHolder is, by default, an instance of ThreadLocalSecurityContextHolderStrategy, which stores SecurityContexts in a ThreadLocal. Therefore, it is not necessarily a good idea to inject the SecurityContext directly into a bean at initialization time - it may need to be retrieved from the ThreadLocal each time, in a multi-threaded environment, so the correct one is retrieved.

vote vote

70

I agree that having to query the SecurityContext for the current user stinks, it seems a very un-Spring way to handle this problem.

I wrote a static "helper" class to deal with this problem; it's dirty in that it's a global and static method, but I figured this way if we change anything related to Security, at least I only have to change the details in one place:

/** * Returns the domain User object for the currently logged in user, or null * if no User is logged in. *  * @return User object for the currently logged in user, or null if no User *         is logged in. */ public static User getCurrentUser() {      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()      if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();      // principal object is either null or represents anonymous user -     // neither of which our domain User object can represent - so return null     return null; }   /**  * Utility method to determine if the current user is logged in /  * authenticated.  * <p>  * Equivalent of calling:  * <p>  * <code>getCurrentUser() != null</code>  *   * @return if user is logged in  */ public static boolean isLoggedIn() {     return getCurrentUser() != null; } 
vote vote

67

To make it just show up in your JSP pages, you can use the Spring Security Tag Lib:

http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html

To use any of the tags, you must have the security taglib declared in your JSP:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %> 

Then in a jsp page do something like this:

<security:authorize access="isAuthenticated()">     logged in as <security:authentication property="principal.username" />  </security:authorize>  <security:authorize access="! isAuthenticated()">     not logged in </security:authorize> 

NOTE: As mentioned in the comments by @SBerg413, you'll need to add

use-expressions="true"

to the "http" tag in the security.xml config for this to work.

vote vote

60

If you are using Spring Security ver >= 3.2, you can use the @AuthenticationPrincipal annotation:

@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {     String currentUsername = currentUser.getUsername();     // ... } 

Here, CustomUser is a custom object that implements UserDetails that is returned by a custom UserDetailsService.

More information can be found in the @AuthenticationPrincipal chapter of the Spring Security reference docs.

Top 3 video Explaining java - When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?

Related QUESTION?