我有一个使用Spring Security的Spring MVC Web应用程序 . 我想知道当前登录用户的用户名 . 我正在使用下面给出的代码段 . 这是接受的方式吗?
我不喜欢在这个控制器中调用静态方法 - 这违背了Spring的全部目的,恕我直言 . 有没有办法配置应用程序以注入当前的SecurityContext或当前的身份验证?
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request...) {
final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
...
}
17 回答
如果您使用Spring 3,最简单的方法是:
自从回答这个问题后,Spring世界发生了很多变化 . Spring简化了当前用户在控制器中的使用 . 对于其他bean,Spring采用了作者的建议并简化了“SecurityContextHolder”的注入 . 更多细节在评论中 .
这是我最终选择的解决方案 . 而不是在我的控制器中使用
SecurityContextHolder
,我想注入一些在引擎盖下使用SecurityContextHolder
的内容,但是从我的代码中抽象出类似单例的类 . 除了滚动我自己的界面之外我没有办法做到这一点,如下所示:现在,我的控制器(或任何POJO)看起来像这样:
并且,由于接口是解耦点,因此单元测试非常简单 . 在这个例子中,我使用Mockito:
接口的默认实现如下所示:
最后, 生产环境 Spring配置如下所示:
Spring,一个所有东西的依赖注入容器,似乎没有提供注入类似东西的方法,这似乎有点愚蠢 . 我理解
SecurityContextHolder
是从acegi继承的,但仍然 . 问题是,它们非常接近 - 如果只有SecurityContextHolder
有一个getter来获取底层的SecurityContextHolderStrategy
实例(这是一个接口),你可以注入它 . 事实上,我甚至opened a Jira issue就是这样 .最后一件事 - 我很好奇但是,正如一位同事向我指出的那样,我以前的答案在多线程环境中不起作用 .
SecurityContextHolder
使用的基础SecurityContextHolderStrategy
默认情况下是ThreadLocalSecurityContextHolderStrategy
的实例,它将SecurityContext
存储在ThreadLocal
中 . 因此,在初始化时将SecurityContext
直接注入bean不一定是个好主意 - 每次都需要在多线程环境中从ThreadLocal
检索它,以便检索正确的 .我同意不得不为当前用户查询SecurityContext,这似乎是一种处理这个问题的非Spring方式 .
我写了一个静态的“帮助器”类来处理这个问题;它很脏,因为它是一个全局和静态的方法,但是如果我们更改与安全相关的任何内容,我就会这样想,至少我只需要在一个地方更改细节:
要使它只显示在JSP页面中,您可以使用Spring Security Tag Lib:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html
要使用任何标记,必须在JSP中声明安全性标记库:
然后在jsp页面中执行以下操作:
注意:如@ SBerg413的评论中所述,您需要添加
到security.xml配置中的“http”标记,以使其工作 .
如果您使用的是Spring Security ver> = 3.2,则可以使用
@AuthenticationPrincipal
注释:这里,
CustomUser
是一个自定义对象,它实现UserDetails
,由自定义UserDetailsService
返回 .可以在Spring Security参考文档的@AuthenticationPrincipal章节中找到更多信息 .
我通过HttpServletRequest.getUserPrincipal()获得经过身份验证的用户;
例:
在Spring 3中,您有以下选项 .
选项1 :
选项2:
选项3:
选项4:花哨的一个:Check this out for more details
是的,静态通常很糟糕 - 通常,但在这种情况下,静态是您可以编写的最安全的代码 . 由于安全上下文将Principal与当前运行的线程相关联,因此最安全的代码将尽可能直接地从线程访问静态 . 隐藏在注入的包装类后面的访问权限为攻击者提供了更多攻击点 . 他们不需要访问代码(如果jar被签名,他们将很难改变它们),他们只需要一种覆盖配置的方法,这可以在运行时完成或将一些XML滑入类路径 . 即使在签名代码中使用注释注入也可以使用外部XML进行覆盖 . 这样的XML可能会为正在运行的系统注入一个流氓主体 . 这可能就是为什么Spring在这种情况下做的事情就像Spring一样 .
我会这样做:
对于我写的最后一个Spring MVC应用程序,我没有注入SecurityContext持有者,但我确实有一个基本控制器,我有两个与此相关的实用方法...isAuthenticated()&getUsername() . 在内部,他们执行您描述的静态方法调用 .
至少那时如果你需要稍后重构它只会在一次 .
你可以使用Spring AOP aproach . 例如,如果您有一些服务,则需要了解当前主体 . 您可以引入自定义注释,即@Principal,它表示此服务应该是主体依赖的 .
然后在你的建议中,我认为需要扩展MethodBeforeAdvice,检查特定服务是否有@Principal注释并注入Principal名称,或者将其设置为'ANONYMOUS' .
唯一的问题是,即使在使用Spring Security进行身份验证之后,容器中也不存在用户/主体bean,因此依赖注入它将很困难 . 在我们使用Spring Security之前,我们将创建一个具有当前Principal的会话范围的bean,将其注入“AuthService”,然后将该Service注入Application中的大多数其他服务 . 所以这些服务只需调用authService.getCurrentUser()来获取对象 . 如果您在代码中有一个位置,您在会话中获得对同一Principal的引用,则可以将其设置为会话范围bean上的属性 .
如果您使用Spring 3并且需要在控制器中使用经过身份验证的主体,那么最佳解决方案是执行以下操作:
我在
@Controller
类中使用@AuthenticationPrincipal
注释以及在@ControllerAdvicer
注释中使用@AuthenticationPrincipal
注释 . 例:UserActive
是我用于记录用户服务的类,并从org.springframework.security.core.userdetails.User
扩展 . 就像是:真的很容易
试试这个
将
Principal
定义为控制器方法中的依赖项,spring将在调用时在方法中注入当前经过身份验证的用户 .我想在freemarker页面上分享我支持用户详细信息的方式 . 一切都很简单,工作完美!
您只需要在
default-target-url
(表单登录后的页面)上放置身份验证重新请求这是我对该页面的控制方法:这是我的ftl代码:
就是这样,用户名将在授权后出现在每个页面上 .