Search code examples
spring-bootspring-securityspring-oauth2

Spring: forwarding to /oauth/token endpoint loses authentication


I'm building a Spring Boot authorization server which needs to generate Oauth2 tokens with two different auth methods. I want to have a different endpoint for each method, but by default Spring only creates /oauth/token, and while it can be changed, I don't think it is possible to have two different paths for it.

As an alternative, I'm trying to create two methods in a controller which do an internal forward to /oauth/token, adding a parameter to the request so I can know where it came from.

I have something like this:

@RequestMapping(value = "/foo/oauth/token", method = RequestMethod.POST)
public ModelAndView fooOauth(ModelMap model) {
    model.addAttribute("method", "foo");
    return new ModelAndView("forward:/oauth/token", model);
}

This performs the forward correctly, but the auth fails with:

There is no client authentication. Try adding an appropriate authentication filter.

The same request works correctly when sent to /oauth/token directly, so I'm guessing that the problem is that the BasicAuthenticationFilter is not running after the forward.

How can I make it work?


Solution

  • Looking carefully to the filter chains created for the Oauth endpoints, and for the forwarding controllers, it's easy to see that the latter are missing the BasicAuthenticationFilter, because they aren't authenticated, and auth isn't performed again after the forward.

    To solve it, I created a new config like this:

    @Configuration
    public class ForwarderSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
    
        @Autowired
        private FooClientDetailsService fooClientDetailsService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
            for (AuthorizationServerConfigurer configurerBit : configurers) configurerBit.configure(configurer);
            http.apply(configurer);
            http
                    .authorizeRequests()
                        .antMatchers("/foo/oauth/token").fullyAuthenticated()
                    .and()
                        .requestMatchers()
                        .antMatchers("/foo/oauth/token");
            http.setSharedObject(ClientDetailsService.class, fooClientDetailsService);
    
        }
    
    }
    

    This code mimics what Spring Oauth does behind the scenes (here), running identical filter chains with the same authentication options on both endpoints.

    When the /oauth/token endpoint finally runs, it finds the auth results that it expects, and everything works.

    Finally, if you want to run a different ClientDetailsService on two forwarding endpoints, you just have to create two configuration classes like this one, and replace the ClientDetailsService on the setSharedObject call in each of them. Note that for this, you'll have to set different @Order values in each class.