如何获取活动用户的UserDetails

时间:2012-01-06 21:07:45

标签: java spring spring-security

在我的控制器中,当我需要活动(登录)用户时,我正在执行以下操作来实现UserDetails

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

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

例如:

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

但我没有获得UsernamePasswordAuthenticationToken,而是获得了UserDetails

我正在寻找一个优雅的解决方案。有什么想法吗?

8 个答案:

答案 0 :(得分:207)

序言:从Spring-Security 3.2开始,在这个答案的最后描述了一个很好的注释@AuthenticationPrincipal。当您使用Spring-Security> = 3.2时,这是最好的方法。

当你:

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

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

(顺便说一句:我的回答有点老了(2012年1月),所以Lukas Schmelzeisen作为第一个基于Spring Security 3.2的@AuthenticationPrincipal注释解决方案出现。 )


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

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;
        }
   }
}

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

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

在配置中,您只需添加:

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

@See:Learn to customize Spring MVC @Controller method arguments

应该注意的是,如果你使用的是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;
          }
     }
}

在配置中,您需要添加此

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

@See Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface


Spring-Security 3.2解决方案

Spring Security 3.2(不要与Spring 3.2混淆)在解决方案中有自己的构建:@AuthenticationPrincipalorg.springframework.security.web.bind.annotation.AuthenticationPrincipal)。这在Lukas Schmelzeisen`s answer

中有详细描述

这只是写作

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

要实现这一点,您需要注册AuthenticationPrincipalArgumentResolverorg.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver):通过“激活”@EnableWebMvcSecurity或在mvc:argument-resolvers内注册此bean - 以同样的方式我用上面的may Spring 3.1解决方案描述了它。

@See Spring Security 3.2 Reference, Chapter 11.2. @AuthenticationPrincipal


Spring-Security 4.0解决方案

它的工作方式与Spring 3.2解决方案类似,但在Spring 4.0中,@AuthenticationPrincipalAuthenticationPrincipalArgumentResolver被“移动”到另一个包中:

(但旧包中的旧类仍然存在,所以不要混用它们!)

这只是写作

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

要实现这一点,您需要注册(org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver:通过“激活”@EnableWebMvcSecurity或在mvc:argument-resolvers内注册此bean - 以同样的方式我用上面的may Spring 3.1解决方案描述了它。

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@See Spring Security 5.0 Reference, Chapter 39.3 @AuthenticationPrincipal

答案 1 :(得分:61)

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

如果您有[{1}}实施UserDetails,则可以执行以下操作:

CustomUser

请参阅Spring Security Documentation: @AuthenticationPrincipal

答案 2 :(得分:25)

Spring Security旨在与其他非Spring框架一起使用,因此它没有与Spring MVC紧密集成。默认情况下,Spring Security会从Authentication方法返回HttpServletRequest.getUserPrincipal()对象,这样您就可以获得作为主体的内容。您可以使用

直接从中获取UserDetails对象
UserDetails ud = ((Authentication)principal).getPrincipal()

另请注意,对象类型可能会有所不同,具体取决于所使用的身份验证机制(例如,您可能无法获得UsernamePasswordAuthenticationToken),而Authentication并不一定要包含{{1} }}。它可以是字符串或任何其他类型。

如果您不想直接调用UserDetails,最优雅的方法(我将遵循)是注入您自己的自定义安全上下文访问器界面,该界面可根据您的需求和用户对象类型进行自定义。使用相关方法创建接口,例如:

SecurityContextHolder

然后,您可以通过访问标准实现中的interface MySecurityAccessor { MyUserDetails getCurrentUser(); // Other methods } 来实现此功能,从而完全将代码与Spring Security分离。然后将其注入需要访问当前用户的安全信息或信息的控制器中。

另一个主要好处是很容易使用固定数据进行简单的实现测试,而不必担心填充线程本地等等。

答案 3 :(得分:9)

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

答案 4 :(得分:8)

从Spring Security 3.2版开始,由一些较旧的答案实现的自定义功能以@AuthenticationPrincipal支持的AuthenticationPrincipalArgumentResolver注释的形式开箱即用。

它的一个简单例子是:

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

CustomUser需要从authentication.getPrincipal()

分配

这是相应的Javadocs AuthenticationPrincipalAuthenticationPrincipalArgumentResolver

答案 5 :(得分:5)

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

答案 6 :(得分:0)

如果您需要模板中的授权用户(例如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>

答案 7 :(得分:0)

您可以尝试以下操作: 通过使用Spring的Authentication Object,我们可以在controller方法中从中获取User详细信息。下面是示例,通过在controller方法中传递Authentication对象以及参数。一旦对用户进行身份验证,详细信息就会填充到Authentication Object中。

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}