如何获得活跃用户的UserDetails
在我的控制器中,当我需要活动(login)的用户,我正在做以下来获得我的UserDetails
实现:
User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); log.debug(activeUser.getSomeCustomField());
它工作正常,但我认为spring可以在这样的情况下使生活更轻松。 有没有办法让UserDetails
自动装入控制器或方法?
例如,像这样的东西:
public ModelAndView someRequestHandler(Principal principal) { ... }
但是,而不是得到UsernamePasswordAuthenticationToken
,我得到一个UserDetails
?
我正在寻找一个优雅的解决scheme。 有任何想法吗?
序言:自从Spring-Security 3.2以来,在这个答案的末尾描述了一个很好的注解@AuthenticationPrincipal
。 这是使用Spring-Security> = 3.2时最好的方法。
当你:
- 使用旧版本的Spring-Security,
- 需要从数据库加载一个自定义的用户对象的一些信息(如login或ID)存储在主体或
- 想要了解一个
HandlerMethodArgumentResolver
或WebArgumentResolver
如何以优雅的方式解决这个问题,或者只是想了解@AuthenticationPrincipal
和AuthenticationPrincipalArgumentResolver
背后的背景(因为它基于HandlerMethodArgumentResolver
)
然后继续阅读 – 否则只需使用@AuthenticationPrincipal
并感谢Rob Winch( @AuthenticationPrincipal
作者)和Lukas Schmelzeisen (他的回答)。
(顺便说一句:我的答案有点老(2012年1月),所以Lukas Schmelzeisen是基于Spring Security 3.2的@AuthenticationPrincipal
注解解决scheme中的第一个。)
然后你可以在你的控制器中使用
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 {}
在configuration中你只需要添加这个:
<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,他们推荐使用WebArgumentResolver的HandlerMethodArgumentResolver。 – 看Jay的评论
与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; } } }
在configuration中你需要添加这个
<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解决scheme
Spring Security 3.2(不要和Spring 3.2混淆)有一个自己的解决scheme: @AuthenticationPrincipal
。 这在Lukas Schmelzeisen的答案中有很好的描述
这只是写作
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) { ... }
为了得到这个工作,你需要注册AuthenticationPrincipalArgumentResolver
:通过“激活” @EnableWebMvcSecurity
或者在mvc:argument-resolvers
注册这个bean – 就像我之前用Spring 3.1的解决scheme描述的那样。
@查看Spring Security 3.2参考,第11.2章。 @AuthenticationPrincipal
虽然Ralphs Answer提供了一个优雅的解决scheme,但Spring Security 3.2不再需要实现自己的ArgumentResolver
。
如果你有一个UserDetails
实现CustomUser
你可以这样做:
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messags for this user and return them ... }
请参阅Spring Security文档:@AuthenticationPrincipal
Spring Security旨在与其他非Spring框架一起工作,因此它不与Spring MVC紧密集成。 Spring Security默认从HttpServletRequest.getUserPrincipal()
方法返回Authentication
对象,这就是你得到的主体。 你可以通过使用直接获取你的UserDetails
对象
UserDetails ud = ((Authentication)principal).getPrincipal()
另请注意,对象types可能因使用的身份validation机制而异(例如,您可能不会获得UsernamePasswordAuthenticationToken
),并且Authentication
不一定必须包含UserDetails
。 它可以是一个string或任何其他types。
如果你不想直接调用SecurityContextHolder
,最优雅的方法(我会遵循)是注入自定义的安全上下文访问器接口,它是自定义的,以满足您的需求和用户对象types。 用相关的方法创build一个接口,例如:
interface MySecurityAccessor { MyUserDetails getCurrentUser(); // Other methods }
然后,您可以通过访问标准实现中的SecurityContextHolder
来实现此目的,从而完全将您的代码与Spring Security解耦。 然后将其注入到需要访问当前用户的安全信息或信息的控制器中。
另一个主要的好处是可以很简单地使用固定的数据进行testing,而不必担心填充线程本地等等。
实现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()); } }
从Spring Security版本3.2开始,由一些旧的答案实现的自定义function,以AuthenticationPrincipalArgumentResolver
支持的@AuthenticationPrincipal
注释forms@AuthenticationPrincipal
即用。
它的一个简单的例子是:
@Controller public class MyController { @RequestMapping("/user/current/show") public String show(@AuthenticationPrincipal CustomUser customUser) { // do something with CustomUser return "view"; } }
CustomUser需要从authentication.getPrincipal()
以下是AuthenticationPrincipal和AuthenticationPrincipalArgumentResolver的相应Javadoc
@Controller public abstract class AbstractController { @ModelAttribute("loggedUser") public User getLoggedUser() { return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } }
如果你需要在模板(如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>