0

I have a spring boot + angular web app that closely follow the setup of this tutorial: I'm running Authorization Server, Resource Server, UI web app all in the same Spring Boot application.

Then I have the need to allow user to login with third party social login (facebook etc.) and implicitly create user accounts if use hasn't registered previously.

I was trying to accomplish that by following another tutorial here.

In the tutorial, making a GET /login/facebook call would trigger the SSOFilter that was setup in the code. But that's not happening with my own code. I tried to change the order of the SSO filter to different numbers and it didn't help either.

In the logs from the tutorial auth-server example. I see the following output:

2016-09-24 22:59:26.728 DEBUG 59433 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request '/login/facebook' matched by universal pattern '/**'
2016-09-24 22:59:26.728 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2016-09-24 22:59:26.729 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2016-09-24 22:59:26.729 DEBUG 59433 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2016-09-24 22:59:26.729 DEBUG 59433 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2016-09-24 22:59:26.731 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2016-09-24 22:59:26.732 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter'
2016-09-24 22:59:26.736 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
2016-09-24 22:59:26.736 DEBUG 59433 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /login/facebook' doesn't match 'POST /logout
2016-09-24 22:59:26.736 DEBUG 59433 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 6 of 12 in additional filter chain; firing Filter: 'CompositeFilter'
2016-09-24 22:59:26.737 DEBUG 59433 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/login/facebook'; against '/login/facebook'

In my own logs, I don't see the CompositeFilter ever being triggered:

2016-09-24 23:38:54.545 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request '/login/facebook' matched by universal pattern '/**'
2016-09-24 23:38:54.546 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2016-09-24 23:38:54.547 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2016-09-24 23:38:54.547 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', GET]
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/login/facebook'; against '/logout'
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', POST]
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /login/facebook' doesn't match 'POST /logout
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', PUT]
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /login/facebook' doesn't match 'PUT /logout
2016-09-24 23:38:54.548 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', DELETE]
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /login/facebook' doesn't match 'DELETE /logout
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.o.p.a.BearerTokenExtractor         : Token not found in headers. Trying request parameters.
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.o.p.a.BearerTokenExtractor         : Token not found in request parameters.  Not an OAuth2 request.
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] p.a.OAuth2AuthenticationProcessingFilter : No token in request, will continue chain.
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2016-09-24 23:38:54.549 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2016-09-24 23:38:54.550 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2016-09-24 23:38:54.551 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
2016-09-24 23:38:54.551 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
2016-09-24 23:38:54.552 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-09-24 23:38:54.552 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-09-24 23:38:54.552 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/login/facebook'; against '/api/**'
2016-09-24 23:38:54.552 DEBUG 60184 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Public object - authentication not attempted
2016-09-24 23:38:54.553 DEBUG 60184 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login/facebook reached end of additional filter chain; proceeding with original chain
2016-09-24 23:38:54.571 DEBUG 60184 --- [nio-8080-exec-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Looking up handler method for path /login/facebook
2016-09-24 23:38:54.571 DEBUG 60184 --- [nio-8080-exec-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Did not find handler method for [/login/facebook]
2016-09-24 23:38:54.574 DEBUG 60184 --- [nio-8080-exec-1] 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@76f064a2

I'm pulling my hair off yet still clueless what went wrong with my code.

Here's code snippets of my setup:

main class file:

@SpringBootApplication
public class TuangouApplication extends SpringBootServletInitializer {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(TuangouApplication.class, args);
    }

    // this is for WAR file deployment
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(TuangouApplication.class);
    }

    @Bean
    public javax.validation.Validator localValidatorFactoryBean() {
       return new LocalValidatorFactoryBean();
    }
}

WebSecurityConfiguration file. Almost identical to the spring oauth2 tutorial auth-server example.

@Configuration
@EnableOAuth2Client
@Order(6)
public class TuangouConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private OAuth2ClientContext oauth2ClientContext;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off   
        http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**").permitAll()
            .and().exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
            .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
            .and().logout().logoutSuccessUrl("/").permitAll()
            .and().csrf().csrfTokenRepository(csrfTokenRepository())
            .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
        // @formatter:on
    }

//  @Order(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled=true)
    protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
          auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
        }

        @Bean
        public UserDetailsService userDetailsService() {
            return new DatabaseUserServiceDetails();
        }
    }

    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                    HttpServletResponse response, FilterChain filterChain)
                            throws ServletException, IOException {
                CsrfToken csrf = (CsrfToken) request
                        .getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                    String token = csrf.getToken();
                    if (cookie == null
                            || token != null && !token.equals(cookie.getValue())) {
                        cookie = new Cookie("XSRF-TOKEN", token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        System.out.println("### foobar");
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

    @Bean
    @ConfigurationProperties("github")
    public ClientResources github() {
        return new ClientResources();
    }

    @Bean
    @ConfigurationProperties("facebook")
    public ClientResources facebook() {
        return new ClientResources();
    }

    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<Filter>();
        filters.add(ssoFilter(facebook(), "/login/facebook"));
        filters.add(ssoFilter(github(), "/login/github"));
        filter.setFilters(filters);
        return filter;
    }

    private Filter ssoFilter(ClientResources client, String path) {
        OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
                path);
        OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
        filter.setRestTemplate(template);
        filter.setTokenServices(new UserInfoTokenServices(
                client.getResource().getUserInfoUri(), client.getClient().getClientId()));
        return filter;
    }
}

class ClientResources {

    @NestedConfigurationProperty
    private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();

    @NestedConfigurationProperty
    private ResourceServerProperties resource = new ResourceServerProperties();

    public AuthorizationCodeResourceDetails getClient() {
        return client;
    }

    public ResourceServerProperties getResource() {
        return resource;
    }
}

Authorization Server config:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager auth;

    @Autowired
    private DataSource dataSource;

    private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @Bean
    public JdbcTokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    protected AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)
            throws Exception {
        security.passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.authorizationCodeServices(authorizationCodeServices())
                .authenticationManager(auth).tokenStore(tokenStore())
                .approvalStoreDisabled();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter:off
        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);
//          .withClient("grubmarket")
//              .authorizedGrantTypes("password", "authorization_code", "client_credentials",
//                      "refresh_token", "implicit")
//              .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
//              .scopes("read", "write", "trust")
//              .resourceIds("oauth2-resource")
//              .accessTokenValiditySeconds(3600);
        // @formatter:on
    }
}

Resource Server config:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources)
            throws Exception {
        resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.antMatcher("/**").authorizeRequests().antMatchers("/api/**").permitAll();
//      http.antMatcher("/api/**").authorizeRequests().antMatchers(HttpMethod.GET, "/api/deals").permitAll().anyRequest().authenticated();
        // @formatter:on
    }
}

1 Answer 1

1

The default @Order for org.springframework.security.oauth2.config.annotation.web.configuration.ResourcererverConfiguration is 3.

Your TuangouConfiguration is set with @Order(6). So if you change it to @Order(2) the ssoFilter or CompositeFilter will get called when request is GET /login/facebook

Sign up to request clarification or add additional context in comments.

4 Comments

That worked! Where can I find the default order for various filters? And is it possible to print out or log the relative order of all the filters when the program runs?
The default order for the spring security filter chain created by @EnableResourceServer is specified in the javadoc - ...hard-coded Order (of 3). Also, you can debug out the order of filters in their respective chain by overriding the following in your TuangouConfiguration....public void configure(WebSecurity web) throws Exception { web.debug(true); }
Another important configuration to note is HttpSecurity.antMatcher(). I noticed you are using this in TuangouConfiguration and ResourceServerConfiguration but they both are matching on /**. I would recommend using a more specific pattern on ResourceServerConfiguration, for example, /api/**. This would also work and allow your ssoFilter to be called when the request is /login/facebook and would eliminate the need to use @Order on your TuangouConfiguration. I would highly recommend using this configuration approach.
Take a look at this example when configuring Multiple HttpSecurity's

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.