我有一个使用Spring Security的Spring MVC Web应用程序.我想知道当前登录用户的用户名.我正在使用下面给出的代码段.这是接受的方式吗?
我不喜欢在这个控制器中调用静态方法 - 这违背了Spring的全部目的,恕我直言.有没有办法配置应用程序以注入当前的SecurityContext或当前的身份验证?
@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(final HttpServletRequest request...) { final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName(); ... }
tsunade21.. 251
如果您使用的是Spring 3,最简单的方法是:
@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(final HttpServletRequest request, Principal principal) { final String currentUser = principal.getName(); }
Scott Bale.. 64
自从回答这个问题后,Spring世界发生了很多变化.Spring简化了当前用户在控制器中的使用.对于其他bean,Spring采用了作者的建议并简化了"SecurityContextHolder"的注入.更多细节在评论中.
这是我最终选择的解决方案.而不是SecurityContextHolder
在我的控制器中使用,我想注入一些SecurityContextHolder
在引擎盖下使用的东西,但是从我的代码中抽象出类似单例的类.我发现除了滚动我自己的界面之外没办法做到这一点,如下所示:
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配置如下所示:
...
Spring,一个所有东西的依赖注入容器,似乎没有提供注入类似东西的方法,这似乎有点愚蠢.我理解SecurityContextHolder
是从acegi继承而来,但仍然.问题是,它们非常接近 - 如果只有SecurityContextHolder
一个getter来获取底层SecurityContextHolderStrategy
实例(这是一个接口),你可以注入它.事实上,我甚至打开了一个Jira问题.
最后一件事 - 我刚刚改变了我之前的答案.如果你很好奇,请查看历史记录但是,正如同事指出的那样,我之前的回答在多线程环境中不起作用.默认情况下,SecurityContextHolderStrategy
使用的底层SecurityContextHolder
是一个实例ThreadLocalSecurityContextHolderStrategy
,它将SecurityContext
s 存储在a中ThreadLocal
.因此,SecurityContext
在初始化时直接注入bean 并不一定是个好主意- 可能需要ThreadLocal
在多线程环境中每次检索它,以便检索正确的bean .
如果您使用的是Spring 3,最简单的方法是:
@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(final HttpServletRequest request, Principal principal) { final String currentUser = principal.getName(); }
自从回答这个问题后,Spring世界发生了很多变化.Spring简化了当前用户在控制器中的使用.对于其他bean,Spring采用了作者的建议并简化了"SecurityContextHolder"的注入.更多细节在评论中.
这是我最终选择的解决方案.而不是SecurityContextHolder
在我的控制器中使用,我想注入一些SecurityContextHolder
在引擎盖下使用的东西,但是从我的代码中抽象出类似单例的类.我发现除了滚动我自己的界面之外没办法做到这一点,如下所示:
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配置如下所示:
...
Spring,一个所有东西的依赖注入容器,似乎没有提供注入类似东西的方法,这似乎有点愚蠢.我理解SecurityContextHolder
是从acegi继承而来,但仍然.问题是,它们非常接近 - 如果只有SecurityContextHolder
一个getter来获取底层SecurityContextHolderStrategy
实例(这是一个接口),你可以注入它.事实上,我甚至打开了一个Jira问题.
最后一件事 - 我刚刚改变了我之前的答案.如果你很好奇,请查看历史记录但是,正如同事指出的那样,我之前的回答在多线程环境中不起作用.默认情况下,SecurityContextHolderStrategy
使用的底层SecurityContextHolder
是一个实例ThreadLocalSecurityContextHolderStrategy
,它将SecurityContext
s 存储在a中ThreadLocal
.因此,SecurityContext
在初始化时直接注入bean 并不一定是个好主意- 可能需要ThreadLocal
在多线程环境中每次检索它,以便检索正确的bean .
我同意不得不为当前用户查询SecurityContext,这似乎是一种处理这个问题的非Spring方式.
我写了一个静态的"帮助器"类来处理这个问题; 它很脏,因为它是一个全局和静态的方法,但我想这样,如果我们改变任何与安全相关的东西,至少我只需要在一个地方改变细节:
/** * 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. ** Equivalent of calling: *
*
getCurrentUser() != null
* * @return if user is logged in */ public static boolean isLoggedIn() { return getCurrentUser() != null; }
要使它只显示在JSP页面中,您可以使用Spring Security Tag Lib:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html
要使用任何标记,必须在JSP中声明安全性标记库:
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
然后在jsp页面中执行以下操作:
logged in as
not logged in
注意:如@ SBerg413的评论中所述,您需要添加
使用表达式="真"
到security.xml配置中的"http"标记,以使其工作.
如果您使用的是Spring Security ver> = 3.2,则可以使用@AuthenticationPrincipal
注释:
@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) { String currentUsername = currentUser.getUsername(); // ... }
这CustomUser
是一个自定义对象,它实现UserDetails
了自定义返回的对象UserDetailsService
.
可以在Spring Security参考文档的@AuthenticationPrincipal章节中找到更多信息.
我通过HttpServletRequest.getUserPrincipal()获得经过身份验证的用户;
例:
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"; } }
在Spring 3+中,您有以下选项.
选项1 :
@RequestMapping(method = RequestMethod.GET) public String currentUserNameByPrincipal(Principal principal) { return principal.getName(); }
选项2:
@RequestMapping(method = RequestMethod.GET) public String currentUserNameByAuthentication(Authentication authentication) { return authentication.getName(); }
选项3:
@RequestMapping(method = RequestMethod.GET) public String currentUserByHTTPRequest(HttpServletRequest request) { return request.getUserPrincipal().getName(); }
选项4:花哨的一个:查看更多细节
public ModelAndView someRequestHandler(@ActiveUser User activeUser) { ... }
是的,静态通常很糟糕 - 通常,但在这种情况下,静态是您可以编写的最安全的代码.由于安全上下文将Principal与当前运行的线程相关联,因此最安全的代码将尽可能直接地从线程访问静态.隐藏注入的包装类后面的访问权限会为攻击者提供更多攻击点.他们不需要访问代码(如果jar被签名,他们将很难改变它们),他们只需要一种覆盖配置的方法,这可以在运行时完成或将一些XML滑入类路径.即使在签名代码中使用注释注入也可以使用外部XML进行覆盖.这样的XML可能会为正在运行的系统注入一个流氓主体.这可能就是为什么Spring在这种情况下做了类似Spring的事情.
我会这样做:
request.getRemoteUser();