如何获得活跃用户的UserDetails

在我的控制器中,当我需要活动(login)的用户,我正在做以下来获得我的UserDetails实现:

 User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); log.debug(activeUser.getSomeCustomField()); 

它工作正常,但我认为spring可以在这样的情况下使生活更轻松。 有没有办法让UserDetails自动装入控制器或方法?

例如,像这样的东西:

 public ModelAndView someRequestHandler(Principal principal) { ... } 

但是,而不是得到UsernamePasswordAuthenticationToken ,我得到一个UserDetails

我正在寻找一个优雅的解决scheme。 有任何想法吗?

序言:自从Spring-Security 3.2以来,在这个答案的末尾描述了一个很好的注解@AuthenticationPrincipal 。 这是使用Spring-Security> = 3.2时最好的方法。

当你:

  • 使用旧版本的Spring-Security,
  • 需要从数据库加载一个自定义的用户对象的一些信息(如login或ID)存储在主体或
  • 想要了解一个HandlerMethodArgumentResolverWebArgumentResolver如何以优雅的方式解决这个问题,或者只是想了解@AuthenticationPrincipalAuthenticationPrincipalArgumentResolver背后的背景(因为它基于HandlerMethodArgumentResolver

然后继续阅读 – 否则只需使用@AuthenticationPrincipal并感谢Rob Winch( @AuthenticationPrincipal作者)和Lukas Schmelzeisen (他的回答)。

(顺便说一句:我的答案有点老(2012年1月),所以Lukas Schmelzeisen是基于Spring Security 3.2的@AuthenticationPrincipal注解解决scheme中的第一个。)


然后你可以在你的控制器中使用

 public ModelAndView someRequestHandler(Principal principal) { User activeUser = (User) ((Authentication) principal).getPrincipal(); ... } 

没关系,如果你需要一次。 但是,如果你需要它几次丑陋,因为它会污染你的控制器与基础设施的细节,通常应该隐藏的框架。

所以你可能真正想要的是有一个像这样的控制器:

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

因此你只需要实现一个WebArgumentResolver 。 它有一个方法

 Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception 

获取Web请求(第二个参数),并且必须返回User如果它感觉负责方法参数(第一个参数)。

自Spring 3.1以来,有一个叫做HandlerMethodArgumentResolver的新概念。 如果你使用Spring 3.1+,那么你应该使用它。 (这个答案在下一节中有描述))

 public class CurrentUserWebArgumentResolver implements WebArgumentResolver{ Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) { if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) { Principal principal = webRequest.getUserPrincipal(); return (User) ((Authentication) principal).getPrincipal(); } else { return WebArgumentResolver.UNRESOLVED; } } } 

您需要定义自定义注释 – 如果用户的每个实例始终从安全上下文中取出,但不是命令对象,则可以跳过它。

 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ActiveUser {} 

在configuration中你只需要添加这个:

 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" id="applicationConversionService"> <property name="customArgumentResolver"> <bean class="CurrentUserWebArgumentResolver"/> </property> </bean> 

@See: 学习定制Spring MVC @Controller方法参数

应该注意的是,如果你使用Spring 3.1,他们推荐使用WebArgumentResolver的HandlerMethodArgumentResolver。 – 看Jay的评论


与Spring 3.1+的HandlerMethodArgumentResolver相同

 public class CurrentUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterAnnotation(ActiveUser.class) != null && methodParameter.getParameterType().equals(User.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (this.supportsParameter(methodParameter)) { Principal principal = webRequest.getUserPrincipal(); return (User) ((Authentication) principal).getPrincipal(); } else { return WebArgumentResolver.UNRESOLVED; } } } 

在configuration中你需要添加这个

 <mvc:annotation-driven> <mvc:argument-resolvers> <bean class="CurrentUserHandlerMethodArgumentResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven> 

@See 利用Spring MVC 3.1 HandlerMethodArgumentResolver接口


Spring-Security 3.2解决scheme

Spring Security 3.2(不要和Spring 3.2混淆)有一个自己的解决scheme: @AuthenticationPrincipal 。 这在Lukas Schmelzeisen的答案中有很好的描述

这只是写作

 ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) { ... } 

为了得到这个工作,你需要注册AuthenticationPrincipalArgumentResolver :通过“激活” @EnableWebMvcSecurity或者在mvc:argument-resolvers注册这个bean – 就像我之前用Spring 3.1的解决scheme描述的那样。

@查看Spring Security 3.2参考,第11.2章。 @AuthenticationPrincipal

虽然Ralphs Answer提供了一个优雅的解决scheme,但Spring Security 3.2不再需要实现自己的ArgumentResolver

如果你有一个UserDetails实现CustomUser你可以这样做:

 @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messags for this user and return them ... } 

请参阅Spring Security文档:@AuthenticationPrincipal

Spring Security旨在与其他非Spring框架一起工作,因此它不与Spring MVC紧密集成。 Spring Security默认从HttpServletRequest.getUserPrincipal()方法返回Authentication对象,这就是你得到的主体。 你可以通过使用直接获取你的UserDetails对象

 UserDetails ud = ((Authentication)principal).getPrincipal() 

另请注意,对象types可能因使用的身份validation机制而异(例如,您可能不会获得UsernamePasswordAuthenticationToken ),并且Authentication不一定必须包含UserDetails 。 它可以是一个string或任何其他types。

如果你不想直接调用SecurityContextHolder ,最优雅的方法(我会遵循)是注入自定义的安全上下文访问器接口,它是自定义的,以满足您的需求和用户对象types。 用相关的方法创build一个接口,例如:

 interface MySecurityAccessor { MyUserDetails getCurrentUser(); // Other methods } 

然后,您可以通过访问标准实现中的SecurityContextHolder来实现此目的,从而完全将您的代码与Spring Security解耦。 然后将其注入到需要访问当前用户的安全信息或信息的控制器中。

另一个主要的好处是可以很简单地使用固定的数据进行testing,而不必担心填充线程本地等等。

实现HandlerInterceptor接口,然后将UserDetails注入到每个具有Model的请求中,如下所示:

 @Component public class UserInterceptor implements HandlerInterceptor { ....other methods not shown.... public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if(modelAndView != null){ modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()); } } 

从Spring Security版本3.2开始,由一些旧的答案实现的自定义function,以AuthenticationPrincipalArgumentResolver支持的@AuthenticationPrincipal注释forms@AuthenticationPrincipal即用。

它的一个简单的例子是:

 @Controller public class MyController { @RequestMapping("/user/current/show") public String show(@AuthenticationPrincipal CustomUser customUser) { // do something with CustomUser return "view"; } } 

CustomUser需要从authentication.getPrincipal()

以下是AuthenticationPrincipal和AuthenticationPrincipalArgumentResolver的相应Javadoc

 @Controller public abstract class AbstractController { @ModelAttribute("loggedUser") public User getLoggedUser() { return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } } 

如果你需要在模板(如JSP)中使用授权用户

 <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <sec:authentication property="principal.yourCustomField"/> 

和…一起

  <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${spring-security.version}</version> </dependency>