简体   繁体   English

使用 Spring Security 时,在 bean 中获取当前用户名(即 SecurityContext)信息的正确方法是什么?

[英]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.我有一个使用 Spring Security 的 Spring MVC Web 应用程序。 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.我不喜欢在这个控制器中调用静态方法 - 这违背了 Spring 的全部目的,恕我直言。 Is there a way to configure the app to have the current SecurityContext, or current Authentication, injected instead?有没有办法将应用程序配置为注入当前的 SecurityContext 或当前的身份验证?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }

If you are using Spring 3 , the easiest way is:如果您使用的是Spring 3 ,最简单的方法是:

 @RequestMapping(method = RequestMethod.GET)   
 public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

     final String currentUser = principal.getName();

 }

A lot has changed in the Spring world since this question was answered.自从回答这个问题以来,Spring 世界发生了很多变化。 Spring has simplified getting the current user in a controller. Spring 简化了在控制器中获取当前用户的过程。 For other beans, Spring has adopted the suggestions of the author and simplified the injection of 'SecurityContextHolder'.对于其他bean,Spring采纳了作者的建议,简化了'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.我不想在我的控制器中使用SecurityContextHolder ,而是想注入一些在底层使用SecurityContextHolder东西,但从我的代码中抽象出类似单例的类。 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:现在,我的控制器(或任何 POJO)看起来像这样:

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:在这个例子中,我使用 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:最后,生产 Spring 配置如下所示:

<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. Spring,一个所有事物的依赖注入容器,没有提供一种注入类似东西的方法,这似乎有点愚蠢。 I understand SecurityContextHolder was inherited from acegi, but still.我知道SecurityContextHolder是从 acegi 继承的,但仍然如此。 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.问题是,它们非常接近 - 如果只有SecurityContextHolder有一个 getter 来获取底层SecurityContextHolderStrategy实例(这是一个接口),您可以注入它。 In fact, I even opened a Jira issue to that effect.事实上,我什至为此打开了一个 Jira 问题

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 SecurityContext s in a ThreadLocal .底层SecurityContextHolderStrategy使用SecurityContextHolder ,默认情况下,的一个实例ThreadLocalSecurityContextHolderStrategy ,其存储SecurityContext S IN一个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.因此,在初始化时将SecurityContext直接注入 bean 不一定是一个好主意 - 在多线程环境中,每次可能需要从ThreadLocal检索它,因此检索到正确的。

I agree that having to query the SecurityContext for the current user stinks, it seems a very un-Spring way to handle this problem.我同意必须查询当前用户的 SecurityContext 很糟糕,这似乎是处理这个问题的一种非常非 Spring 的方式。

I wrote a static "helper" class to deal with this problem;我写了一个静态的“helper”类来处理这个问题; 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;
}

To make it just show up in your JSP pages, you can use the Spring Security Tag Lib:为了让它只显示在你的 JSP 页面中,你可以使用 Spring Security Tag Lib:

http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html 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:要使用任何标签,您必须在 JSP 中声明安全标签库:

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

Then in a jsp page do something like this:然后在一个jsp页面中做这样的事情:

<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注意:正如@SBerg413 在评论中提到的,您需要添加

use-expressions="true"使用表达式=“真”

to the "http" tag in the security.xml config for this to work.到 security.xml 配置中的“http”标记以使其工作。

If you are using Spring Security ver >= 3.2, you can use the @AuthenticationPrincipal annotation:如果您使用的是 Spring Security ver >= 3.2,则可以使用@AuthenticationPrincipal注解:

@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 .在这里, CustomUser是一个自定义对象实现UserDetails由自定义返回UserDetailsService

More information can be found in the @AuthenticationPrincipal chapter of the Spring Security reference docs.更多信息可以在 Spring Security 参考文档的@AuthenticationPrincipal章节中找到。

I get authenticated user by HttpServletRequest.getUserPrincipal();我通过 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";
    }
}

In Spring 3+ you have have following options.在 Spring 3+ 中,您有以下选项。

Option 1 :选项1 :

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

Option 2 :选项2:

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

Option 3:选项 3:

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

}

Option 4 : Fancy one : Check this out for more details选项 4:花式一:查看此了解更多详细信息

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

我只会这样做:

request.getRemoteUser();

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.由于安全上下文将 Principal 与当前运行的线程相关联,因此最安全的代码将尽可能直接地从线程访问静态。 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.他们不需要访问代码(如果 jar 被签名,他们将很难更改代码),他们只需要一种覆盖配置的方法,这可以在运行时完成或将一些 XML 滑到类路径上。 Even using annotation injection in the signed code would be overridable with external XML.即使在签名代码中使用注解注入也可以被外部 XML 覆盖。 Such XML could inject the running system with a rogue principal.这样的 XML 可能会向正在运行的系统注入一个流氓主体。 This is probably why Spring is doing something so un-Spring-like in this case.这可能就是为什么在这种情况下 Spring 会做一些与 Spring 不同的事情。

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().对于我写的最后一个 Spring MVC 应用程序,我没有注入 SecurityContext 持有者,但我确实有一个基本控制器,我有两个与此相关的实用程序方法...... 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.至少,如果您以后需要重构,它只会出现一次。

You could use Spring AOP aproach.您可以使用 Spring AOP 方法。 For example if you have some service, that needs to know current principal.例如,如果您有一些服务,则需要知道当前的委托人。 You could introduce custom annotation ie @Principal , which indicate that this Service should be principal dependent.您可以引入自定义注释,即 @Principal ,这表明该服务应该是主体相关的。

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.然后在我认为需要扩展 MethodBeforeAdvice 的建议中,检查特定服务是否具有 @Principal 注释并注入主体名称,或者将其设置为“ANONYMOUS”。

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.唯一的问题是,即使在使用 Spring Security 进行身份验证后,容器中也不存在用户/主体 bean,因此依赖注入它会很困难。 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.在我们使用 Spring Security 之前,我们将创建一个具有当前 Principal 的 session-scoped bean,将它注入到“AuthService”中,然后将该 Service 注入到应用程序中的大多数其他服务中。 So those Services would simply call authService.getCurrentUser() to get the object.因此,这些服务将简单地调用 authService.getCurrentUser() 来获取对象。 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.如果您的代码中有一个地方可以在会话中获得对同一 Principal 的引用,则只需将其设置为会话范围 bean 上的属性即可。

The best solution if you are using Spring 3 and need the authenticated principal in your controller is to do something like this:如果您使用的是 Spring 3 并且需要在控制器中使用经过身份验证的主体,那么最好的解决方案是执行以下操作:

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

    }

I am using the @AuthenticationPrincipal annotation in @Controller classes as well as in @ControllerAdvicer annotated ones.我现在用的是@AuthenticationPrincipal注释中@Controller类以及在@ControllerAdvicer注释的。 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 .其中UserActive是我用于登录用户服务的类,从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.真的很容易。

Principal定义为控制器方法中的依赖项,spring 将在调用时将当前经过身份验证的用户注入到您的方法中。

Try this尝试这个

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();认证身份验证 = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName(); String userName = authentication.getName();

I like to share my way of supporting user details on freemarker page.我喜欢在 freemarker 页面上分享我支持用户详细信息的方式。 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:您只需要在default-target-url (表单登录后的页面)上放置身份验证重新请求 这是我对该页面的 Controler 方法:

@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:这是我的 ftl 代码:

<@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.就是这样,用户名将在授权后出现在每个页面上。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 spring bean.xml 中指定 xsd 的正确方法是什么 - What is the proper way to specify a xsd within a spring bean.xml 带有Spring Boot的Spring Security 4:静态资源(即CSS,JS,IMG)404问题 - Spring Security 4 with Spring Boot: static resource (i.e. css, js, img) 404 issue Spring Security 5.2 / WebClient 使用用户名和密码连接到另一个服务的方式是什么? - What's the Spring Security 5.2 / WebClient way of using username & password to connect to another service? 在Spring Security 5中仅对某些特定的URL允许分号,即StrictHttpFirewall? - Allow semicolon only for some specific url in Spring Security 5 i.e. StrictHttpFirewall? 使用Weld SE(CDI)异步创建bean的正确方法是什么? - What is the proper way to create a bean asynchronously using Weld SE (CDI)? 由于 spring 上下文的双重加载,SpringBoot 无法将 bean 注入 kafkaConumser 即在 spring 托管类之外的类中 - SpringBoot not able to inject bean into kafkaConumser i.e. in class outside spring managed class due to double loading of spring context 使用Spring Security时如何在速度宏中获取CSRF令牌 - How to obtain csrf token in a velocity macro when using spring security 使用自定义bean spring安全性时避免双重身份验证 - Avoid double authentication when using custom bean spring security 在@Configuration中提供对spring @Bean的依赖关系的正确方法 - Proper way of providing dependency to spring @Bean in @Configuration 错误:使用此用户名的用户不存在:使用 spring 安全性时 - Error: User doesn't exist with this username: when using spring security
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM