如何获取活动用户的UserDetails


170

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

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

它工作正常,但我认为Spring在这种情况下可以使生活更轻松。有没有办法将UserDetails自动接线连接到控制器或方法中?

例如,类似:

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

但是UsernamePasswordAuthenticationToken我得到了,UserDetails而不是得到了?

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

Answers:


226

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

当你:

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

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

(顺便说一句:我的答案有点老(2012年1月),因此是Lukas Schmelzeisen作为第一个使用@AuthenticationPrincipal基于Spring Security 3.2 的注释解决方案的人。)


然后您可以在控制器中使用

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

在配置中,您只需要添加以下内容:

<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,他们建议使用HandlerMethodArgumentResolver而不是WebArgumentResolver。-见周杰伦的评论


HandlerMethodArgumentResolverSpring 3.1+ 相同

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 利用Spring MVC 3.1 HandlerMethodArgumentResolver接口


Spring-Security 3.2解决方案

春季安全3.2(不使用Spring 3.2混淆)在溶液中有自己的编译:@AuthenticationPrincipalorg.springframework.security.web.bind.annotation.AuthenticationPrincipal)。Lukas Schmelzeisen的回答对此进行了很好的描述。

只是在写

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

要使此工作正常进行,您需要注册AuthenticationPrincipalArgumentResolverorg.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver):通过“激活” @EnableWebMvcSecurity或在其中注册此Bean,mvc:argument-resolvers这与我在上面的Spring 3.1解决方案中所描述的方式相同。

@请参阅《Spring Security 3.2参考》的第11.2章。@AuthenticationPrincipal


Spring-Security 4.0解决方案

它的工作方式类似于Spring 3.2解决方案,但是在Spring 4.0中,@AuthenticationPrincipaland AuthenticationPrincipalArgumentResolver被“移动”到另一个软件包中:

(但是旧包中的旧类仍然存在,因此请不要混合使用!)

只是在写

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

要使此工作正常进行,您需要注册(org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver:通过“激活” @EnableWebMvcSecurity或在其中注册此Bean,mvc:argument-resolvers这与我在上面的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参考,第39.3章@AuthenticationPrincipal


或者,只需创建一个具有getUserDetails()方法的bean,然后将@Autowire插入您的控制器即可。
sourcedelica 2012年

实施此解决方案时,我在resolveArgument()的顶部设置了一个断点,但是我的应用程序从未涉足Web参数解析器。您在servlet上下文中而不是在根上下文中的spring配置,对吗?
周杰伦2012年

@Jay:此配置是根上下文的一部分,而不是servlet上下文。-似乎我忘记(id="applicationConversionService")在示例中指定了ID
Ralph

11
应该注意的是,如果您使用的是Spring 3.1,他们建议使用HandlerMethodArgumentResolver而不是WebArgumentResolver。我通过在Servlet上下文中使用<annotation-driven>配置它来使HandlerMethodArgumentResolver工作。除此之外,我实现了此处发布的答案,一切都很好
Jay

@sourcedelica为什么不仅仅创建静态方法?
Alex78191 '17

66

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

如果有UserDetails实现CustomUser,则可以执行以下操作:

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

请参阅Spring Security文档:@AuthenticationPrincipal


2
对于那些不喜欢阅读提供的链接的人,必须通过@EnableWebMvcSecurityXML或以XML 启用:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik 2014年

如何检查是否customUser为空?
2016年

if (customUser != null) { ... }
T3rm1'9

27

Spring Security旨在与其他非Spring框架一起使用,因此未与Spring MVC紧密集成。Spring Security 默认情况下AuthenticationHttpServletRequest.getUserPrincipal()方法返回对象,这就是您作为主体得到的。您可以UserDetails使用以下方法直接从中获取对象

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

还要注意,对象类型可能会根据所使用的身份验证机制而有所不同(UsernamePasswordAuthenticationToken例如,您可能不会获得),Authentication并且不一定严格包含UserDetails。它可以是字符串或任何其他类型。

如果您不想SecurityContextHolder直接调用,最优雅的方法(我会遵循这种方法)是注入您自己的自定义安全上下文访问器接口,该接口是经过定制的,可以满足您的需求和用户对象类型。使用相关方法创建一个接口,例如:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

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

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


我当时正在考虑这种方法,但是我不确定a)如何正确执行正确的方法(tm)和b)是否存在线程问题。您确定那里没有问题吗?我将使用上面发布的注释方法,但是我认为这仍然是解决问题的一种不错的方法。感谢您的发布。:)
Awnry Bear'1

4
从技术上讲,这与从您的控制器直接访问SecurityContextHolder相同,因此不会出现任何线程问题。它只是将呼叫保持在一个地方,并允许您轻松注入替代方案进行测试。您还可以在需要访问安全信息的其他非Web类中重用相同的方法。
肖恩·绵羊

知道...这就是我的想法。如果我对注释方法进行单元测试时遇到问题,或者如果我想减少与Spring的耦合,那么这将是一个很好的选择。
Awnry熊,2012年

@LukeTaylor能否请您进一步解释这种方法,我对Spring和Spring Security有点陌生,所以我不太了解如何实现此方法。我在哪里实现它以便可以访问?到我的UserServiceImpl?
Cu7l4ss 2012年

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

1
谢谢@atrain,它非常有用且优雅。另外,我必须<mvc:interceptors>在应用程序配置文件中添加。
埃里克

最好通过spring-security-taglibs以下方式在模板中获得授权用户:stackoverflow.com/a/44373331/548473
Grigory Kislin

9

从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()

这是AuthenticationPrincipalAuthenticationPrincipalArgumentResolver的相应Javadocs


1
@nbro当我添加特定于版本的解决方案时,没有其他任何解决方案被更新以考虑到该解决方案
geoand

AuthenticationPrincipalArgumentResolver现在已弃用
Igor Donin 18'Apr

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

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>

0

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

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

请详细说明您的答案。
尼古拉·舍甫琴科

我已经编辑了答案,我们可以使用Authentication Object,它具有已通过身份验证的用户的用户详细信息。
Mirza Shujathullah
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.