Spring Security OAuth2 Redirect Loop
I have a spring-boot oauth2 client application with dependencies: - spring-boot 1.2.0.RC1 - spring-security-oauth2 2.0.4.RELEASE - spring-security 3.2.5.RELEASE
The client authenticates, the authentication is set to the SecurityContextHolder, but when the request is redirected to the original URL, the filter chain starts processing again. I noticed that in SecurityContextPersistenceFilter
contextBeforeChainExecution and contextAfterChainExecution both have null authentication.
I based some code on [1] Spring OAuth2 security web application (google) in a redirect loop
Any ideas as to why the redirect loop? Thank you in advance.
[Log snapshots] https://gist.github.com/yterradas/61da3f6eccc683b3a086
Below is the security configuration.
@Configuration
public class SecurityConfig {
@Configuration
@EnableWebMvcSecurity
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter;
@Autowired
private LoginUrlAuthenticationEntryPoint vaultAuthenticationEntryPoint;
@SuppressWarnings ({"SpringJavaAutowiringInspection"})
@Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
@Override
protected void configure (HttpSecurity http) throws Exception {
// @formatter: off
http
.authorizeRequests ()
.antMatchers ("/ **"). authenticated ()
.and ()
.exceptionHandling (). authenticationEntryPoint (vaultAuthenticationEntryPoint)
.and ()
.addFilterAfter (oAuth2ClientContextFilter, ExceptionTranslationFilter.class)
.addFilterBefore (oAuth2ClientAuthenticationProcessingFilter, FilterSecurityInterceptor.class)
.anonymous (). disable ();
// @formatter: on
}
@Override
public void configure (WebSecurity web) throws Exception {
// @formatter: off
web
/ * TODO:
disable debug in production
* /
.debug (true);
// @formatter: on
}
}
@Configuration
@ EnableOAuth2Client
protected static class ClientSecurityConfig {
@Value ("$ {app.name}") private String appId;
@Value ("$ {app.clientId}") private String appClientId;
@Value ("$ {app.clientSecret}") private String appClientSecret;
@Value ("$ {app.redirectUrl}") private String appRedirectUrl;
@Value ("$ {vault.accessTokenUrl}") private String vaultAccessTokenUrl;
@Value ("$ {vault.userAuthorizationUrl}") private String vaultUserAuthorizationUrl;
@Value ("$ {vault.checkTokenUrl}") private String vaultCheckTokenUrl;
@SuppressWarnings ({"SpringJavaAutowiringInspection"})
@Resource
@Qualifier ("oauth2ClientContext")
private OAuth2ClientContext oAuth2ClientContext;
@Autowired
@Qualifier ("securityDataSource")
private DataSource securityDataSource;
@Autowired
private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter;
@Bean
public OAuth2RestOperations oAuth2RestOperations () {
AccessTokenProviderChain provider = new AccessTokenProviderChain (
Arrays.asList (new AuthorizationCodeAccessTokenProvider ())
);
provider.setClientTokenServices (new JdbcClientTokenServices (securityDataSource));
OAuth2RestTemplate template = new OAuth2RestTemplate (oAuth2Resource (), oAuth2ClientContext);
template.setAccessTokenProvider (provider);
template.setMessageConverters (Arrays.asList (jackson2HttpMessageConverter));
return template;
}
@Bean
OAuth2ProtectedResourceDetails oAuth2Resource () {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails ();
resource.setId (appId);
resource.setAuthenticationScheme (AuthenticationScheme.query);
resource.setAccessTokenUri (vaultAccessTokenUrl);
resource.setUserAuthorizationUri (vaultUserAuthorizationUrl);
resource.setUseCurrentUri (false);
resource.setPreEstablishedRedirectUri (appRedirectUrl);
resource.setClientId (appClientId);
resource.setClientSecret (appClientSecret);
resource.setClientAuthenticationScheme (AuthenticationScheme.form);
return resource;
}
@Bean
ResourceServerTokenServices oAuth2RemoteTokenServices () {
VaultTokenServices tokenServices = new VaultTokenServices ();
RestTemplate restOperations = new RestTemplate ();
restOperations.setMessageConverters (Arrays.asList (jackson2HttpMessageConverter));
tokenServices.setRestTemplate (restOperations);
tokenServices.setClientId (appClientId);
tokenServices.setClientSecret (appClientSecret);
tokenServices.setCheckTokenEndpointUrl (vaultCheckTokenUrl);
return tokenServices;
}
@Bean
LoginUrlAuthenticationEntryPoint oAuth2AuthenticationEntryPoint () {
return new LoginUrlAuthenticationEntryPoint ("/ vaultLogin");
}
@Bean
OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter () {
OAuth2ClientAuthenticationProcessingFilter filter =
new OAuth2ClientAuthenticationProcessingFilter ("/ vaultLogin");
filter.setRestTemplate (oAuth2RestOperations ());
filter.setTokenServices (oAuth2RemoteTokenServices ());
return filter;
}
}
} source to share
I found a mediocre solution that entailed removing almost every filter from SecurityFilterChain
. Unfortunately, I don't have a working copy of the application I was working on. However, it should be easy to reproduce the solution by removing as many filters as possible before it breaks the application, then add only the ones you need. If my memory serves me correctly, the culprit was either SecurityContextPersistenceFilter
or RequestCacheAwareFilter
.
source to share
For others who might come here with the same problem - a redirect loop when using Spring Security oAuth. I had this because I am behind a firewall that blocks any direct access to the Internet, and my Spring Boot + Security application needs to call a userinfo IdP endpoint that was located on the Internet, like Okta, Google, etc. You can fix this by setting up a proxy in your local startup configuration like this:
-Dhttp.proxyHost=yourhttpproxy.company.com
-Dhttp.proxyPort=yourhttproxyport
-Dhttp.nonProxyHosts='localhost|*.yourintranetdomain1.com|*.yourintranetdomain2.com|etc'
-Dhttps.proxyHost=yourhttpsproxy.company.com
-Dhttps.proxyPort=yourhttpsproxyport
-Dhttps.nonProxyHosts='localhost|*.yourintranetdomain1.com|*.yourintranetdomain2.com|etc'
Hope this helps.
source to share