如何获取活跃用户的 UserDetails

Posted

技术标签:

【中文标题】如何获取活跃用户的 UserDetails【英文标题】:How to get active user's 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

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

【问题讨论】:

【参考方案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 被“移动”到另一个包:

org.springframework.security.core.annotation.AuthenticationPrincipal org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver

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

只是写而已

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获取模板中的授权用户:***.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 AuthenticationPrincipal 和 AuthenticationPrincipalArgumentResolver

【讨论】:

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


【讨论】:

以上是关于如何获取活跃用户的 UserDetails的主要内容,如果未能解决你的问题,请参考以下文章

如何在Spring Security中获取用户名(在我的情况下是电子邮件)[UserDetails / String]

如何获取每月 7 天活跃用户?

如何使用 Flutter 从 Firebase 获取当前活跃用户?

如何从 Discord OAUTH2 获取响应并将其转换为我自己的 UserDetails,我可以在整个代码中使用它

Spring security UserDetails 和控制器(获取用户)

在运行时获取 Spring UserDetails 对象