如何获取活跃用户的 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)从数据库中加载您的自定义用户对象或 想了解HandlerMethodArgumentResolver
或WebArgumentResolver
如何优雅地解决这个问题,或者只是想了解@AuthenticationPrincipal
和AuthenticationPrincipalArgumentResolver
背后的背景(因为它基于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 中,@AuthenticationPrincipal
和 AuthenticationPrincipalArgumentResolver
被“移动”到另一个包:
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 中启用:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
如何判断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,这很有用而且很优雅。另外,我必须在我的应用程序配置文件中添加<mvc:interceptors>
。
最好通过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]
如何使用 Flutter 从 Firebase 获取当前活跃用户?
如何从 Discord OAUTH2 获取响应并将其转换为我自己的 UserDetails,我可以在整个代码中使用它