Spring Security向控制器注入自定义主体

Cem*_*emo 5 spring spring-security

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

我想注入自定义Principal:

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
}
Run Code Online (Sandbox Code Playgroud)

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

这是注射校长:

else if (Principal.class.isAssignableFrom(paramType)) {
    return request.getUserPrincipal();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

    return auth;
 }
Run Code Online (Sandbox Code Playgroud)

因为一个Authentication也是一个Principal.(到目前为止我不喜欢弹簧安全的唯一部分.我也会问这个问题.)

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

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

作为额外信息:

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

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

  auth
     .userDetailsService(detailsService);
}
Run Code Online (Sandbox Code Playgroud)

有帮助吗?

Rob*_*nch 14

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

我为你看了几个选项.每个都有一些优点和缺点,但我认为最好的是使用@ModelAttribute@ControllerAdvice

@ModelAttribute@ControllerAdvice

最简单的选项是使用@ModelAttributeon custom 注释方法@ControllerAdvice.您可以在Spring Reference中找到详细信息.

@ControllerAdvice
public class SecurityControllerAdvice {

    @ModelAttribute
    public UserPrincipal customPrincipal(Authentication a) {
        return (UserPrincipal) a == null ? null : a.getPrincipal();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

请注意,@ModelAttribute只有确保@ModelAttribute在HttpServletRequest#getPrincipal()上使用它才是必需的.如果它没有实现Principal,@ModelAttribute则不是必需的.

@Value 和ExpressionValueMethodArgumentResolver

你也可以这样做:

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

这是因为HttpServletRequest可用作ExpressionValueMethodArgumentResolver的属性(由Spring MVC默认添加),允许通过SpEL访问内容.我发现这不如注释@ModelAttribute中必须存在的常量那么吸引人@Value.SPR-10760解析后会更好,这样可以使用自己的自定义注释,如:

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

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

子类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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*erl 7

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

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

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

这将是开箱即用的,不需要额外的配置。

  • 好点子。顺便说一句,@Rob Winch 实现了 `@AuthenticationPrincipal`/`AuthenticationPrincipalArgumentResolver` :) (2认同)