如何获取活跃用户的 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 对象