如何获取活动用户的用户详细信息

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

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

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

例如:

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

但是我得到的不是 UsernamePasswordAuthenticationToken而是 UserDetails

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

132240 次浏览

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

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

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

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

interface MySecurityAccessor {


MyUserDetails getCurrentUser();


// Other methods
}

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

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

前言: 由于 Spring-Security 3.2,在这个答案的末尾有一个很好的注释 @AuthenticationPrincipal。这是使用 Spring-Security > = 3.2时的最佳方法。

当你:

  • 使用旧版本的 Spring-Security
  • 需要通过存储在主体或
  • 想了解如何 HandlerMethodArgumentResolverWebArgumentResolver可以解决这一优雅的方式,或只是想了解背后的背景 @AuthenticationPrincipalAuthenticationPrincipalArgumentResolver(因为它是基于一个 HandlerMethodArgumentResolver)

然后继续阅读ーー否则就用 @AuthenticationPrincipal,感谢罗布•温奇(《 @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;
}
}
}

您需要定义自定义注释——如果 User 的每个实例都应该始终从安全上下文中获取,但从来不是命令对象,那么您可以跳过它。

@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


对于 Spring 3.1 + ,HandlerMethodArgumentResolver也是如此

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解决方案

Spring Security 3.2(不要与 Spring 3.2混淆)在解决方案中有自己的构建: @AuthenticationPrincipal(org.springframework.security.web.bind.annotation.AuthenticationPrincipal)。这在 Lukas Schmelzeisen 的回答中得到了很好的描述

只是写作而已

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

为了让它工作,你需要注册 AuthenticationPrincipalArgumentResolver(org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) : 要么通过“激活”@EnableWebMvcSecurity,要么通过在 mvc:argument-resolvers中注册这个 bean ——就像我在上面的 Spring 3.1解决方案中描述的那样。

@ See Spring Security 3.2参考文献,第11.2章.@AuthenticationPresident


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>

@ See Spring Security 5.0参考文献,第39.3章@AuthenticationPrime

实现 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());
}
}
@Controller
public abstract class AbstractController {
@ModelAttribute("loggedUser")
public User getLoggedUser() {
return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}

虽然 拉尔夫答案提供了一个优雅的解决方案,但是使用 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 安全文档:@AuthenticationPrime

从 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 身份验证主体 > 和 < a href = “ http://docs.spring.io/spring-security/site/docs/3.2.0.RELEASE/apidocs/org/springFramework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.html”rel = “ noReferrer”> AuthenticationPrincipalArgumentResolver

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

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

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

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

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

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