【问题标题】:Injecting Custom Principal to Controllers by Spring SecuritySpring Security 将自定义主体注入控制器
【发布时间】:2013-07-18 11:38:42
【问题描述】:

servletApi() 对 Spring Security 的支持很棒。

我想像这样注入自定义主体:

public interface UserPrincipal extends Principal {
   public Integer getId();
}

@RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipal user){
   // implementation
}  

or


@RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipalImpl user){
   // implementation
}

Spring 支持在 ServletRequestMethodArgumentResolver 的帮助下注入 Principal 实例。

它是这样注入主体的:

else if (Principal.class.isAssignableFrom(paramType)) {
    return request.getUserPrincipal();
}

这是问题开始的地方。 requestSecurityContextHolderAwareRequestWrapper 的一个实例。它有一个实现:

@Override
public Principal getUserPrincipal() {
    Authentication auth = getAuthentication();

    if ((auth == null) || (auth.getPrincipal() == null)) {
        return null;
    }

    return auth;
 }

因为Authentication 也是Principal。 (到目前为止,我唯一不喜欢 Spring Security 的部分。我也会单独问这个问题。)

这导致了一个问题。因为AuthenticationPrincipal 而不是UserPrincipal

我该如何解决这个问题?我是否也需要实现作为 UserPrincipal 的身份验证?或者我应该更改 HandlerMethodArgumentResolver 命令来创建自定义解析器? (这对于 Spring MVC 来说并不容易,因为内部处理程序具有更高的优先级。)

作为额外信息:

我正在使用 Spring Security M2,我对 AuthenticationManagerBuilder 的配置很简单:

@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception        {

  auth
     .userDetailsService(detailsService);
}

有什么帮助吗?

【问题讨论】:

  • 这对你有帮助吗:stackoverflow.com/questions/8764545/… -- 是一个稍微不同的问题,但我认为你最终会尝试解决同样的问题。
  • 谢谢@Ralph,但它帮不了我。我的情况与那个问题略有不同。有很大的不同,也不喜欢显式的身份验证,这意味着我的模块也必须依赖于 Spring Security。

标签: spring spring-security


【解决方案1】:

从根本上说,这似乎是与 Spring MVC 集成的麻烦,而不是 Spring 安全问题。 Spring Security 无法知道 Authentication@getPrinicpal() 实现了 Principal,因为 API 返回了一个 Object。

我看到了一些适合您的选择。每个都有一些优点和缺点,但我认为最好的是使用 @ModelAttribute@ControllerAdvice

@ModelAttribute@ControllerAdvice

最简单的选择是在自定义@ControllerAdvice 上使用@ModelAttribute 注释方法。您可以在the Spring Reference找到详细信息。

@ControllerAdvice
public class SecurityControllerAdvice {

    @ModelAttribute
    public UserPrincipal customPrincipal(Authentication a) {
        return (UserPrincipal) a == null ? null : a.getPrincipal();
    }
}

现在在您的控制器中,您可以执行以下操作:

@RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(@ModelAttribute UserPrincipal user){
   // implementation
}

请注意,@ModelAttribute 仅用于确保在 HttpServletRequest#getPrincipal() 上使用 @ModelAttribute。如果没有实现 Principal,则不需要@ModelAttribute

@Value 和 ExpressionValueMethodArgumentResolver

你也可以这样做:

@RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(
  @Value("#{request.userPrincipal.principal}") UserPrincipal user){
   // implementation
}

这是有效的,因为 HttpServletRequest 可作为 ExpressionValueMethodArgumentResolver 的属性(由 Spring MVC 默认添加),它允许通过 SpEL 访问事物。由于@Value 注释中必须包含常量,我发现这不如@ModelAttribute 有吸引力。当SPR-10760 被解析时会更好,这将允许您使用自己的自定义注释,例如:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value("#{request.userPrincipal.principal}")
public @interface CurrentUser { }

@Autowire RequestMappingHandlerAdapter

这有点草率,因为 RequestMappingHandlerAdapter 已经被初始化了,但是你可以改变 HandlerMethodArgumentResolvers 的顺序,如下所示:

@EnableWebMvc
@Configuration
public class WebMvcConfiguration 
  extends WebMvcConfigurerAdapter {
    ...
    @Autowired
    public void setArgumentResolvers(RequestMappingHandlerAdapter adapter) {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
        resolvers.add(new CustomPrincipalArgumentResolver());
        resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
        adapter.setArgumentResolvers(resolvers);
    }
}

子类 WebMvcConfigurationSupport

您还可以扩展 WebMvcConfigurationSupport 而不是使用 @EnableWebMvc 以确保首先使用您的 HandlerMethodArgumentResolver。例如:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
    ...

    @Bean
    @Override
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter()();
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
        resolvers.add(new CustomPrincipalArgumentResolver());
        resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
        adapter.setArgumentResolvers(resolvers);
        return adapter;
    }
}

【讨论】:

  • 罗伯,非常感谢。你真的教会了我很多东西。 :) 今天我已经实现了一个自定义方法参数处理程序。为了改变处理程序的顺序,我不得不使用反射:) 你的实现比我的好。谢谢
  • 优秀的答案。很好解释。谢谢。
【解决方案2】:

我知道这是一个老问题,但由于在搜索注入 Principal 时它确实在 Google 上名列前茅,我将发布 2020 年更新:

从 Spring Security 4.0 开始,您只需将 @AuthenticationPrincipal 注入控制器方法即可:

@RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(@AuthenticationPrincipal UserPrincipal user){
   // implementation
} 

这将开箱即用,无需额外配置。

【讨论】:

  • 好点。顺便说一句,是@Rob Winch 实现了@AuthenticationPrincipal/AuthenticationPrincipalArgumentResolver :)
猜你喜欢
  • 2011-02-02
  • 2022-01-08
  • 2023-04-08
  • 2015-02-17
  • 1970-01-01
  • 2017-06-04
  • 1970-01-01
  • 2014-08-23
  • 2016-12-07
相关资源
最近更新 更多