Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting Custom Principal to Controllers by Spring Security

servletApi() support of Spring Security is great.

I want to inject custom Principal as this:

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 has support for injecting Principal instances with the help of ServletRequestMethodArgumentResolver.

It is injecting principal as this:

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

Here is the place where the problem begins. request is here an instance of SecurityContextHolderAwareRequestWrapper. It has an implementation of:

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

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

    return auth;
 }

Because an Authentication is also an Principal. (The only part of spring security I did not like so far. I will ask this a separate question as well.)

This is causing a problem. Because Authentication is a Principal not a UserPrincipal.

How can I resolve this problem? Do I need to implement an authentication which is a UserPrincipal as well? Or should I change HandlerMethodArgumentResolver order a create a custom resolver? (This is not easy for Spring MVC because internal handlers has higher priority.)

As a extra information:

I am using Spring Security M2 and my configuration for AuthenticationManagerBuilder is simply:

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

  auth
     .userDetailsService(detailsService);
}

Any help?

like image 356
Cemo Avatar asked Jul 19 '13 08:07

Cemo


People also ask

What is SecurityContextHolder getContext () getAuthentication ()?

The HttpServletRequest.getUserPrincipal() will return the result of SecurityContextHolder.getContext().getAuthentication() . This means it is an Authentication which is typically an instance of UsernamePasswordAuthenticationToken when using username and password based authentication.

What is SecurityContextHolder in spring?

The SecurityContextHolder is a helper class, which provide access to the security context. By default, it uses a ThreadLocal object to store security context, which means that the security context is always available to methods in the same thread of execution, even if you don't pass the SecurityContext object around.


2 Answers

Fundamentally this seems like trouble integrating with Spring MVC and not a Spring Security issue. Spring Security has no way of knowing that Authentication@getPrinicpal() implements Principal since the API returns an Object.

I see a few options for you. Each has some pros and cons, but I think the best is using @ModelAttribute and @ControllerAdvice

@ModelAttribute and @ControllerAdvice

The easiest option is annotate a method with @ModelAttribute on custom @ControllerAdvice. You can find details in the Spring Reference.

@ControllerAdvice
public class SecurityControllerAdvice {

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

Now in your controller you can do something like this:

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

Note that the @ModelAttribute is necessary only to ensure the @ModelAttribute is used over the HttpServletRequest#getPrincipal(). If it did not implement Principal, @ModelAttribute is not required.

@Value and ExpressionValueMethodArgumentResolver

You can also do something like this:

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

This works because the HttpServletRequest is available as an attribute to the ExpressionValueMethodArgumentResolver (added by default by Spring MVC) which allows accessing things via SpEL. I find this less attractive than @ModelAttribute due to the constant that must be in the @Value annotation. It will be nicer when SPR-10760 is resolved which would allow for your own custom annotation to be used like:

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

@Autowire RequestMappingHandlerAdapter

This is a bit sloppy because the RequestMappingHandlerAdapter has already been initialized, but you can change the ordering of the HandlerMethodArgumentResolvers as shown here:

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

Subclass WebMvcConfigurationSupport

You can also extend WebMvcConfigurationSupport instead of using @EnableWebMvc to ensure your HandlerMethodArgumentResolver is used first. For example:

@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;
    }
}
like image 62
Rob Winch Avatar answered Nov 15 '22 23:11

Rob Winch


I know this is an old question, but as it does come up on top on Google when searching for injecting a Principal, I'll post a 2020 update:

Since Spring Security 4.0 you can just simply inject an @AuthenticationPrincipal into your controller methods:

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

This will work out of the box, no additional config required.

like image 30
Stefan Haberl Avatar answered Nov 15 '22 21:11

Stefan Haberl