I'm using Spring Boot 1.5.13 and with that Spring Security 4.2.6 and Spring Security oAuth2 2.0.15.
I want to find a best practice setup for our Spring Boot applications that serve a mixed set of content: A REST API, and some web pages that provide a convenience "landing page" for developers with some links on it, plus Swagger based API documentation, which is also web content.
I have a configuration that allows me to run the app with proper authorization code flow, hence I can access all web content via Browser and get authenticated by the configured IdP (in my case PingFederate), plus I can make API calls from within the Browser, i.e. directly or with a REST Client, e.g. with RESTClient.
This is my security configuration:
@Slf4j
@Configuration
@EnableWebSecurity
@EnableOAuth2Sso // this annotation must stay here!
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login**", "/webjars/**", "/css/**").permitAll()
.antMatchers("/cfhealth").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/protected", "/api/**").authenticated();
}
@Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
and the oAuth2 configuration:
@Configuration
@Slf4j
public class OAuth2Config extends ResourceServerConfigurerAdapter {
@Value("${pingfederate.pk-uri}")
String pingFederatePublicKeyUri;
@Autowired
PingFederateKeyUtils pingFederateKeyUtils;
@Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String certificate = pingFederateKeyUtils.getKeyFromServer(pingFederatePublicKeyUri);
String publicKey = pingFederateKeyUtils.extractPublicKey(certificate);
converter.setVerifier(pingFederateKeyUtils.createSignatureVerifier(publicKey));
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
But when I want to call a REST API programmatically/outside the Browser with a bearer token in the header, e.g. with curl, the authorization code flow kicks in and redirects to the local login endpoint. What I want is that API calls accept the bearer token for authentication, without creating a session, and that all web content/mvc calls in the Browser establish a session.
curl -i -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8080/authdemo/api/hello
Adding the @EnableResourceServer annotation to the above SecurityConfig class (and adding security.oauth2.resource.filter-order=3 in the application properties file, I can make the curl command work, but then the authorization code flow is broken, I get the following output in the Browser for all URLs in my application:
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
Now is there a way to get this szenario working nicely? If yes, how would that look like? Or is it only supported in later versions of Spring Boot+Security+oAuth2?
The question at Spring Boot with Security OAuth2 - how to use resource server with web login form? is quite similar