6
votes

I have a oauth2 client spring-boot 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 in the SecurityContextHolder but when the request is redirected to the original url the filter chain starts processing again. I noticed that in the SecurityContextPersistenceFilter the contextBeforeChainExecution and contextAfterChainExecution both have a null authentication.

I have based some of the code on [1]Spring Security OAuth2 (google) web app in redirect loop

Any ideas as to why the redirect loop? Thank you in advance.

[Logs snippet]https://gist.github.com/yterradas/61da3f6eccc683b3a086

Below is the config for security.

@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;
    }

  }
}
3
Where's the authorization server. Is its check token endpoint available (can you curl it)?Dave Syer
The Authorization Server is a separate component, not in the same project. Yes, I can curl it. I can see the authorization being created, as well as the redirect to the saved request is initiated. I am not sure but I think the context is not being saved. The reason for thinking that is because the Context before and after execution has null authentication.Yoandy Terradas
I asked specifically if you can curl the /check_token endpoint. It's not part of the spec so depending in the implementation it may or may not be there (and your client appears to be trying to use it).Dave Syer
@DaveSyer Yes, I can curl it. Also, I have a custom RemoteTokenServices which handle the validation because the Authorization server does not validate using http-basic. I have updated the question with a log snippet. Perhaps that can help.Yoandy Terradas

3 Answers

2
votes

I think you have 2 OAuth2ClientContextFilters (one is added by @EnableOAuth2Client and you have added another manually to the Spring Security filter chain). You should be able to remove the one you added.

0
votes

For others that might come here with the same issue - a redirect loop when using Spring Security oAuth. I had this because I'm behind a firewall that blocks any direct access to the Internet, and my Spring Boot+Security application needs to call the userinfo endpoint of an IdP that was located on the Internet, e.g. Okta, Google, etc. You might fix it by configuring your proxy in your local run configuration, as follows:

-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”

I hope this helps.

0
votes

I found a mediocre solution which entailed removing almost every filter from the SecurityFilterChain. Unfortunately, I do not have a working copy of the application I worked on. However, it should be easy to replicate the solution by removing as many filters as possible before it breaks the application then add only the necessary ones. If memory serves me right there the culprit was either SecurityContextPersistenceFilter or RequestCacheAwareFilter.