0

I have already setup a standalone OAuth2 Client and Authorization-Server provider using Spring Boot oAuth2 support. I am now trying to create a protected API Resource Server as documented here:

@SpringBootApplication
@EnableResourceServer
public class HelloService extends ResourceServerConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(HelloService.class, args);
    }
}

My Hello servlet controller is similarly trivial:

@RestController
public class HelloController {
   @CrossOrigin
   @RequestMapping("/hello")
   public String index() {
      return "Greeting from Hello Service!";
   }
}

I have set security.oauth2.resource.user-info-uri:http://localhost:9999/user in my application.properties. From my reading of the Spring Boot documentation that is all that is required. Spring boot should pick up spring-boot-starter-security and spring-security-oauth2 from the classpath - automatically requiring authentication for the /hello endpoint. The oAuth2 filter chain should pick up the bearer access token from the Authorization header and call the configured Authorization-Server endpoint to check the access token. However in the browser I see the following response:

WWW-Authenticate:Bearer realm="oauth2-resource", error="unauthorized", 
error_description="Full authentication is required to access this resource"

Which seems to imply that at least the header is being recognised. I then opened up access to the /hello endpoint:

@SpringBootApplication
@EnableResourceServer
public class HelloService extends ResourceServerConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(HelloService.class, args);
    }

   @Override
   public void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests().anyRequest().permitAll();
   }
}

I can then access the /hello endpoint. I can also see that the Authorization-Server endpoint http://localhost:9999/user is being hit because if I change the port number to 9998 then the following error appears in the resource service log "http://localhost:9998/user": Connection refused

There is no Connection refused error when the /hello endpoint is protected. So the problem is that the oAuth2 filter chain is not calling the remote Authorization-Server endpoint when the /hello endpoint is protected. What I don't understand is why?

With authentication disabled permitAll() I added my own TokenExtractor and set log level to debug:

hello.MyTokenExtractor: **** Calling MyTokenExtractor.extract(): org.springframework.security.web.firewall.RequestWrapper
hello.MyTokenExtractor: Header token value: 1611e720-9213-4e0e-bfb3-a6b926335ab7
o.s.b.a.s.o.r.UserInfoTokenServices: Getting user info from: http://localhost:9999/user
o.s.s.oauth2.client.OAuth2RestTemplate: Created GET request for "http://localhost:9999/user"
o.s.s.oauth2.client.OAuth2RestTemplate   : Setting request Accept header to [application/json, application/*+json]

You can see the header token - the request to the autentication server and all is good. However when i call authenticated() the authorization token has disappeared.

hello.MyTokenExtractor: **** Calling MyTokenExtractor.extract(): org.springframework.security.web.firewall.RequestWrapper
hello.MyTokenExtractor: Header token value: null
hello.MyTokenExtractor: Token not found in headers. Trying request parameters.
hello.MyTokenExtractor: Token not found in request parameters.  Not an OAuth2 request. 

Full trace is as follows:

org.eclipse.jetty.server.Server         : REQUEST OPTIONS /hello on HttpChannelOverHttp@7f06f300{r=1,c=false,a=DISPATCHED,uri=//localhost:8000/hello}
o.e.jetty.server.handler.ContextHandler  : scope null||/hello @ o.s.b.c.e.j.JettyEmbeddedWebAppContext@7ce6a65d{/,[file:///private/var/folders/z5/wh0gsly536zbv68jbny_jzmswqk01z/T/jetty-docbase.5203414264432039078.8000/],AVAILABLE}
o.e.jetty.server.handler.ContextHandler  : context=||/hello @ o.s.b.c.e.j.JettyEmbeddedWebAppContext@7ce6a65d{/,[file:///private/var/folders/z5/wh0gsly536zbv68jbny_jzmswqk01z/T/jetty-docbase.5203414264432039078.8000/],AVAILABLE}
org.eclipse.jetty.server.session         : sessionHandler=org.eclipse.jetty.server.session.SessionHandler352359770==dftMaxIdleSec=1800
org.eclipse.jetty.server.session         : session=null
o.eclipse.jetty.servlet.ServletHandler   : servlet |/hello|null -> dispatcherServlet@7ef5559e==org.springframework.web.servlet.DispatcherServlet,jsp=null,order=-1,inst=true
o.eclipse.jetty.servlet.ServletHandler   : chain=characterEncodingFilter->hiddenHttpMethodFilter->httpPutFormContentFilter->requestContextFilter->springSecurityFilterChain->Jetty_WebSocketUpgradeFilter->dispatcherServlet@7ef5559e==org.springframework.web.servlet.DispatcherServlet,jsp=null,order=-1,inst=true
o.eclipse.jetty.servlet.ServletHandler   : call filter characterEncodingFilter
o.eclipse.jetty.servlet.ServletHandler   : call filter hiddenHttpMethodFilter
o.eclipse.jetty.servlet.ServletHandler   : call filter httpPutFormContentFilter
o.eclipse.jetty.servlet.ServletHandler   : call filter requestContextFilter
o.s.b.w.f.OrderedRequestContextFilter    : Bound request context to thread: Request(OPTIONS //localhost:8000/hello)@5b6aa5a
o.eclipse.jetty.servlet.ServletHandler   : call filter springSecurityFilterChain
o.s.b.f.s.DefaultListableBeanFactory     : Returning cached instance of singleton bean 'springSecurityFilterChain'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/css/**']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/css/**'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/js/**']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/js/**'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/images/**']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/images/**'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/webjars/**']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/webjars/**'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/**/favicon.ico']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/**/favicon.ico'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/error']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/hello'; against '/error'
o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
o.s.security.web.FilterChainProxy        : /hello at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
o.s.security.web.FilterChainProxy        : /hello at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
o.s.security.web.FilterChainProxy        : /hello at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@7d694ffe
o.s.security.web.FilterChainProxy        : /hello at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', GET]
o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /hello' doesn't match 'GET /logout
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', POST]
o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /hello' doesn't match 'POST /logout
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', PUT]
o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /hello' doesn't match 'PUT /logout
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', DELETE]
o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /hello' doesn't match 'DELETE /logout
o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
o.s.security.web.FilterChainProxy        : /hello at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
hello.MyTokenExtractor                   : **** Calling MyTokenExtractor.extract(): org.springframework.security.web.firewall.RequestWrapper

Problem is the CORS preflight OPTIONS request. The following change fixes:

public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated();
            .and().cors();
}
  • When the '/hello' endpoint is protected the authentication server IS NOT called - which is the problem. So there is no log output. When the '/hello' endpoint is not protected the authentication server sends back the authorizations as expected `sendResponse info=null content=DirectByteBuffer@59597ce[p=0,l=1467,c=32768,r=1467]={<<<{"authorities":[{...,"name":"mike"}` – James Turner Nov 21 '17 at 09:52
  • Sorry - copy and paste error in the sample code. Have corrected it now. I believe the protection is automatic when Spring Boot sees classes from `spring-boot-starter-security` on the classpath. I can verify `/hello` is protected when this is the case. To unprotect it I simply added the `configure()` method shown to the `HelloService` class – James Turner Nov 21 '17 at 10:27
  • I have set log level to DEBUG - output is added to initial post. In summary when I turn authentication on, something removes the Authorization header from the request! – James Turner Nov 24 '17 at 16:28
  • The request is an OPTIONS call, because it seems to be the preflight request of CORS. In this case the client must remove the `Authentication` header. That shouldn't be a problem. – dur Nov 24 '17 at 17:22
  • 1
    Good spot on the OPTIONS call. That is the problem - no Authorization header sent on the preflight request. I have added my fix to bottom of issue. – James Turner Nov 26 '17 at 08:42
  • updated fix to include `.cors()` call mentioned in linked issue – James Turner Nov 26 '17 at 10:44

0 Answers0