ChatGPT解决这个技术问题 Extra ChatGPT

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

I have a Spring MVC web app which uses Spring Security. I want to know the username of the currently logged in user. I'm using the code snippet given below . Is this the accepted way?

I don't like having a call to a static method inside this controller - that defeats the whole purpose of Spring, IMHO. Is there a way to configure the app to have the current SecurityContext, or current Authentication, injected instead?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
Why dont you have a controller(secure controller) as a super class get the user from the SecurityContext and set it as an instance variable inside that class? This way when you extend the secure controller, your whole class will have access to the current context's User principal.

n
nbro

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();

 }

P
Patrick

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.


I like your solution -- it's a clever use of the factory-method support in Spring. That said, this is working for you because the controller object is scoped to the web request. If you changed the scope of the controller bean in the wrong way, this would break.
The previous two comments refer to an old, incorrect answer which I've just replaced.
Is this still the recommended solution with the current Spring release? I can't believe it needs so much code just to retrieve just the username.
If you're using Spring Security 3.0.x, they implemented my suggestion in the JIRA issue I logged jira.springsource.org/browse/SEC-1188 so you can now inject the SecurityContextHolderStrategy instance (from SecurityContextHolder) directly into your bean via standard Spring configuration.
Please see tsunade21 answer. Spring 3 now allows you to use java.security.Principal as a method argument in your controller
m
matt b

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;
}

it is as long as SecurityContextHolder.getContext() is, and the latter is threadsafe since it keeps the security details in a threadLocal. This code maintains no state.
D
Dani

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.


This seems like it's probably the Spring Security-approved way!
For this method to work, you need to add use-expressions="true" to the http tag in the security.xml config.
Thanks @SBerg413, I'll edit my answer and add your important clarification!
m
matsev

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.


d
digz6666

I get authenticated user by HttpServletRequest.getUserPrincipal();

Example:

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;

import foo.Form;

@Controller
@RequestMapping(value="/welcome")
public class IndexController {

    @RequestMapping(method=RequestMethod.GET)
    public String getCreateForm(Model model, HttpServletRequest request) {

        if(request.getUserPrincipal() != null) {
            String loginName = request.getUserPrincipal().getName();
            System.out.println("loginName : " + loginName );
        }

        model.addAttribute("form", new Form());
        return "welcome";
    }
}

I like Your solution. To Spring specialists: Is that secure, good solution ?
Not a good solution. You will get null if user is authenticated anonymously (http > anonymous elements in Spring Security XML). SecurityContextHolder or SecurityContextHolderStrategy is the proper way.
So that I've checked if not null request.getUserPrincipal() != null.
Is null in Filter
C
Community

In Spring 3+ you have have following options.

Option 1 :

@RequestMapping(method = RequestMethod.GET)    
public String currentUserNameByPrincipal(Principal principal) {
    return principal.getName();
}

Option 2 :

@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
    return authentication.getName();
}

Option 3:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserByHTTPRequest(HttpServletRequest request) {
    return request.getUserPrincipal().getName();

}

Option 4 : Fancy one : Check this out for more details

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

Since 3.2, spring-security-web comes with @CurrentUser which works like the custom @ActiveUser from your link.
@MikePartridge, I don't seem to find what you saying, any link?? or more info??
My mistake - I misunderstood the Spring Security AuthenticationPrincipalArgumentResolver javadoc. An example is shown there wrapping @AuthenticationPrincipal with a custom @CurrentUser annotation. Since 3.2 we don't need to implement a custom argument resolver as in the linked answer. This other answer has more detail.
J
JMax

I would just do this:

request.getRemoteUser();

That might work, but not reliably. From javadoc: "Whether the user name is sent with each subsequent request depends on the browser and type of authentication." -download-llnw.oracle.com/javaee/6/api/javax/servlet/http/…
This is actually a valid and very simple way to get the remote username in a Spring Security web application. The standard filter chain includes a SecurityContextHolderAwareRequestFilter which wraps the request and implements this call by accessing the SecurityContextHolder.
M
Michael Bushe

Yes, statics are generally bad - generally, but in this case, the static is the most secure code you can write. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection in the signed code would be overridable with external XML. Such XML could inject the running system with a rogue principal. This is probably why Spring is doing something so un-Spring-like in this case.


R
RichH

For the last Spring MVC app I wrote, I didn't inject the SecurityContext holder, but I did have a base controller that I had two utility methods related to this ... isAuthenticated() & getUsername(). Internally they do the static method call you described.

At least then it's only in once place if you need to later refactor.


P
Pavel Rodionov

You could use Spring AOP aproach. For example if you have some service, that needs to know current principal. You could introduce custom annotation i.e. @Principal , which indicate that this Service should be principal dependent.

public class SomeService {
    private String principal;
    @Principal
    public setPrincipal(String principal){
        this.principal=principal;
    }
}

Then in your advice, which I think needs to extend MethodBeforeAdvice, check that particular service has @Principal annotation and inject Principal name, or set it to 'ANONYMOUS' instead.


I need to access Principal inside the service class could you post a complete example on github ? I don't know spring AOP, hence the request.
c
cliff.meyers

The only problem is that even after authenticating with Spring Security, the user/principal bean doesn't exist in the container, so dependency-injecting it will be difficult. Before we used Spring Security we would create a session-scoped bean that had the current Principal, inject that into an "AuthService" and then inject that Service into most of the other services in the Application. So those Services would simply call authService.getCurrentUser() to get the object. If you have a place in your code where you get a reference to the same Principal in the session, you can simply set it as a property on your session-scoped bean.


M
Mark

The best solution if you are using Spring 3 and need the authenticated principal in your controller is to do something like this:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

    @Controller
    public class KnoteController {
        @RequestMapping(method = RequestMethod.GET)
        public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {

            if (authToken instanceof UsernamePasswordAuthenticationToken) {
                user = (User) authToken.getPrincipal();
            }
            ...

    }

Why are you doing that instanceof UsernamePasswordAuthenticationToken check when the parameter is already of type UsernamePasswordAuthenticationToken?
(authToken instanceof UsernamePasswordAuthenticationToken) is functional equivalent of if (authToken !=null). The latter might be little cleaner but otherwise there is no difference.
T
Tito

Try this

Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String userName = authentication.getName();


Calling SecurityContextHolder.getContext() static method is exactly what I was complaining about in the original question. You haven't answered anything.
It is, however, exactly what the documentation recommends: static.springsource.org/spring-security/site/docs/3.0.x/… So what are you accomplishing by avoiding it? You're looking for a complicated solution to a simple problem. At best -- you get the same behavior. At worst, you get a bug or security hole.
@BobKerns For testing, it's cleaner to be able to inject the authentication as opposed to putting it on a thread local.
E
EliuX

I am using the @AuthenticationPrincipal annotation in @Controller classes as well as in @ControllerAdvicer annotated ones. Ex.:

@ControllerAdvice
public class ControllerAdvicer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);


    @ModelAttribute("userActive")
    public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
    {
        return currentUser;
    }
}

Where UserActive is the class i use for logged users services, and extends from org.springframework.security.core.userdetails.User. Something like:

public class UserActive extends org.springframework.security.core.userdetails.User
{

    private final User user;

    public UserActive(User user)
    {
        super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
        this.user = user;
    }

     //More functions
}

Really easy.


I
Imrank

Define Principal as a dependency in your controller method and spring will inject the current authenticated user in your method at invocation.


T
Tuna

I like to share my way of supporting user details on freemarker page. Everything is very simple and working perfectly!

You just have to place Authentication rerequest on default-target-url (page after form-login) This is my Controler method for that page:

@RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
    showRequestLog("monitoring");


    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userName = authentication.getName();
    //create a new session
    HttpSession session = request.getSession(true);
    session.setAttribute("username", userName);

    return new ModelAndView(catalogPath + "monitoring");
}

And this is my ftl code:

<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize> 

And that's it, username will appear on every page after authorisation.


Thanks for trying to answer, but the use of the static method SecurityContextHolder.getContext() is exactly what I was wanting to avoid, and the reason I asked this question in the first place.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now