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

2022-08-31 05:08:57

我有一个使用Spring Security的Spring MVC Web应用程序。我想知道当前登录用户的用户名。我正在使用下面给出的代码片段。这是公认的方式吗?

我不喜欢在这个控制器中调用静态方法 - 这违背了Spring的整个目的,恕我直言。有没有办法将应用程序配置为注入当前的SecurityContext或当前的身份验证?

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

答案 1

如果您使用的是Spring 3,最简单的方法是:

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

     final String currentUser = principal.getName();

 }

答案 2

自从这个问题得到回答以来,春天的世界发生了很多变化。Spring简化了将当前用户放入控制器的过程。对于其他豆类,Spring采纳了作者的建议,简化了“SecurityContextHolder”的注入。更多细节在评论中。


这是我最终采用的解决方案。我不想在我的控制器中使用,而是想注入一些在引擎盖下使用的东西,但从我的代码中抽象出那个类似单例的类。除了滚动我自己的界面之外,我没有找到其他方法,就像这样:SecurityContextHolderSecurityContextHolder

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

现在,我的控制器(或任何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
  }

}

而且,由于接口是解耦点,因此单元测试非常简单。在这个例子中,我使用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();
  }

}

接口的默认实现如下所示:

public class SecurityContextHolderFacade implements SecurityContextFacade {

  public SecurityContext getContext() {
    return SecurityContextHolder.getContext();
  }

  public void setContext(SecurityContext securityContext) {
    SecurityContextHolder.setContext(securityContext);
  }

}

最后,生产中的 Spring 配置如下所示:

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

Spring,一个包含所有事物的依赖注入容器,似乎没有提供一种注入类似东西的方法,这似乎有点愚蠢。我知道是从阿杰吉继承的,但仍然如此。问题是,它们非常接近 - 如果只有一个 getter 来获取底层实例(这是一个接口),你可以注入它。事实上,我甚至为此打开了一个Jira问题SecurityContextHolderSecurityContextHolderSecurityContextHolderStrategy

最后一件事 - 我刚刚大大改变了我以前在这里的答案。如果您好奇,请查看历史记录,但是,正如一位同事向我指出的那样,我之前的答案在多线程环境中不起作用。默认情况下,使用的基础是 的实例,它将 s 存储在 中。因此,在初始化时将 直接注入 Bean 并不一定是一个好主意 - 在多线程环境中,可能需要每次都从 中检索它,因此检索正确的 bean。SecurityContextHolderStrategySecurityContextHolderThreadLocalSecurityContextHolderStrategySecurityContextThreadLocalSecurityContextThreadLocal


推荐