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