【问题标题】:How to get active user's UserDetails如何获取活跃用户的 UserDetails
【发布时间】:2012-02-04 13:53:07
【问题描述】:

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

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

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

例如:

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

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

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

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

    序言:自 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;
            }
       }
    }
    

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

    @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>
    

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

    需要注意的是,如果你使用的是 Spring 3.1,他们推荐 HandlerMethodArgumentResolver 而不是 WebArgumentResolver。 - 见 Jay 的评论


    HandlerMethodArgumentResolver 与 Spring 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>
    

    @见Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface


    Spring-Security 3.2 解决方案

    Spring Security 3.2(不要与 Spring 3.2 混淆)有自己的内置解决方案:@AuthenticationPrincipal (org.springframework.security.web.bind.annotation.AuthenticationPrincipal)。这在Lukas Schmelzeisen`s answer中有很好的描述

    只是写而已

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

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

    @见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>
    

    @见Spring Security 5.0 Reference, Chapter 39.3 @AuthenticationPrincipal

    【讨论】:

    • 或者只创建一个具有 getUserDetails() 方法的 bean,然后将其 @Autowire 放入您的控制器中。
    • 为了实现这个解决方案,我在 resolveArgument() 的顶部设置了一个断点,但我的应用程序从未进入 Web 参数解析器。您在 servlet 上下文中的 spring 配置不是根上下文,对吧?
    • @Jay:此配置是根上下文的一部分,而不是 servlet 上下文。 -- 接缝我忘记在示例中指定 id (id="applicationConversionService")
    • 应该注意的是,如果您使用的是 Spring 3.1,他们建议使用 HandlerMethodArgumentResolver 而不是 WebArgumentResolver。我通过在 servlet 上下文中使用 配置它来让 HandlerMethodArgumentResolver 工作。除此之外,我实现了此处发布的答案,一切正常
    • @sourcedelica 为什么不直接创建静态方法?
    【解决方案2】:

    虽然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 Documentation: @AuthenticationPrincipal

    【讨论】:

    • 对于那些不喜欢阅读提供的链接的人,这必须通过@EnableWebMvcSecurity 或在XML 中启用:&lt;mvc:annotation-driven&gt; &lt;mvc:argument-resolvers&gt; &lt;bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /&gt; &lt;/mvc:argument-resolvers&gt; &lt;/mvc:annotation-driven&gt;
    • 如何判断customUser是否为空?
    • if (customUser != null) { ... }
    • 如何使用 mockito 模拟这个带注释的参数?
    【解决方案3】:

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

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

    另请注意,对象类型可能会因所使用的身份验证机制而异(例如,您可能不会得到UsernamePasswordAuthenticationToken)并且Authentication 不一定必须包含UserDetails。它可以是字符串或任何其他类型。

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

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

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

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

    【讨论】:

    • 我正在考虑这种方法,但我不确定 a)如何以正确的方式(tm)和 b)如果会有任何线程问题。你确定那里不会有问题吗?我将使用上面发布的注释方法,但我认为这仍然是一种不错的方法。感谢您的发布。 :)
    • 这在技术上与直接从控制器访问 SecurityContextHolder 相同,因此不应该存在任何线程问题。它只是将调用保持在一个地方,并允许您轻松注入替代方案进行测试。您还可以在其他需要访问安全信息的非 Web 类中重复使用相同的方法。
    • 知道了...这就是我的想法。如果我在使用注解方法进行单元测试时遇到问题,或者我想减少与 Spring 的耦合,这将是一个很好的选择。
    • @LukeTaylor 你能否进一步解释一下这种方法,我对 Spring 和 Spring Security 有点陌生,所以我不太明白如何实现这个。我在哪里实现它以便可以访问它?到我的 UserServiceImpl?
    【解决方案4】:

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

    【讨论】:

    • 感谢@atrain,这很有用而且很优雅。另外,我必须在我的应用程序配置文件中添加&lt;mvc:interceptors&gt;
    • 最好通过spring-security-taglibs获取模板中的授权用户:stackoverflow.com/a/44373331/548473
    【解决方案5】:

    从 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

    【讨论】:

    • @nbro 当我添加特定于版本的解决方案时,其他解决方案都没有更新以考虑此解决方案
    • AuthenticationPrincipalArgumentResolver 现已弃用
    【解决方案6】:
    @Controller
    public abstract class AbstractController {
        @ModelAttribute("loggedUser")
        public User getLoggedUser() {
            return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        }
    }
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        你可以试试这个: 通过使用 Spring 的身份验证对象,我们可以在控制器方法中从中获取用户详细信息。下面是示例,通过在控制器方法中传递身份验证对象以及参数。一旦用户通过身份验证,详细信息将填充到身份验证对象中。

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

        【讨论】:

        • 请详细说明你的答案。
        • 我已经编辑了我的答案,我们可以使用身份验证对象,其中包含已通过身份验证的用户的用户详细信息。
        【解决方案9】:

        要获取活动用户详细信息,您可以在控制器中使用 @AuthenticationPrincipal,如下所示:-

        public String function(@AuthenticationPrincipal UserDetailsImpl user,
                            Model model){   
            model.addAttribute("username",user.getName()); //this user object 
        contains user details 
            return "";
        }
        

        UserDetailsImpl.java

        import com.zoom.model.User;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.security.core.GrantedAuthority;
        import org.springframework.security.core.authority.SimpleGrantedAuthority;
        import org.springframework.security.core.userdetails.UserDetails;
        
        import java.util.Collection;
        import java.util.List;
        public class UserDetailsImpl implements UserDetails {
        
        @Autowired
        private User user;
        
        public UserDetailsImpl(User user) {
            this.user = user;
        }
        
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ADMIN");
            return List.of(simpleGrantedAuthority);
        }
        
        @Override
        public String getPassword() {
            return user.getPassword();
        }
        
        @Override
        public String getUsername() {
            return user.getEmail();
        }
        
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
        
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
        
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
        
        @Override
        public boolean isEnabled() {
            return true;
        }
        
        public String getRole(){
            return user.getRole();
        }
        
        public String getName(){
            return user.getUsername();
        }
        
        public User getUser(){
            return user;
        }
        }
        

        【讨论】:

          猜你喜欢
          • 2020-02-04
          • 2021-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多