10

Spring Security 的 servletApi() 支持很棒。

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

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 支持PrincipalServletRequestMethodArgumentResolver.

它正在注入本金:

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

这是问题开始的地方。request这里是SecurityContextHolderAwareRequestWrapper. 它有一个实现:

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

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

    return auth;
 }

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

这导致了一个问题。因为Authentication是 aPrincipal不是UserPrincipal

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

作为额外信息:

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

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

  auth
     .userDetailsService(detailsService);
}

有什么帮助吗?

4

2 回答 2

21

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

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

@ModelAttribute@ControllerAdvice

@ModelAttribute最简单的选择是使用on custom注释方法@ControllerAdvice您可以在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仅需要确保@ModelAttribute在 HttpServletRequest#getPrincipal() 上使用 。如果它没有实现 Principal,@ModelAttribute则不需要。

@Value和 ExpressionValueMethodArgumentResolver

你也可以这样做:

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

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

@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;
    }
}
于 2013-07-19T16:44:34.007 回答
11

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

从 Spring Security 4.0 开始,你可以简单地将一个注入@AuthenticationPrincipal到你的控制器方法中:

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

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

于 2020-05-26T06:16:01.063 回答