0

I am not a Java programmer by profession. I am working on building an app for our lab. It started out as a Spring MVC app with JSP files. It has now migrated to a Spring REST API that uses OAUTH2 as a standalone authentication and authorization server. I am using a MySQL database to store my data and users. I can successfully get an access_token issued if I use Postman to access the server. However if I use the Angular 2 client I setup I cannot get past the pre-flight OPTIONS request sent by the client to the server. I have tried several different ways to configure a CORS filter, but I cannot get that filter to get implemented and I always get a 401 code returned for the pre-flight request. I have tried implementing the suggested solution in the Spring blog https://spring.io/blog/2015/06/08/cors-support-in-spring-framework but that hasn't worked yet.

In my pom.xml file I am using these versions of Spring

Spring Framework - 4.3.2.RELEASE
Spring Security  - 4.1.2.RELEASE
Spring Security OAUTH2 - 2.0.10.RELEASE

I also included Spring Boot 1.4.0 when trying to use FilterRegistrationBean

Here is my code as it is right now.

WebConfig Class:
@EnableWebMvc
@Configuration
@ComponentScan(basePackages={"eng.lab"})
@Import({ApplicationContext.class})
@PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{

/*  @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }*/

}

SecurityConfig Class:

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;


     @Autowired
     public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
       auth.jdbcAuthentication().dataSource(dataSource)
      .usersByUsernameQuery(
       "select username,password, enabled from user where username=?")
      .authoritiesByUsernameQuery(
       "select u.username, r.role from User u, Role r where r.id=u.roleId and u.userName=?");
     }  


     @Autowired
     private ClientDetailsService clientDetailsService;


            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                .csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
                //.requestMatchers(CorsUtils::isCorsRequest).permitAll()
                .antMatchers("/oauth/token").permitAll();
           }

            @Override
            @Bean
            public AuthenticationManager authenticationManagerBean() throws Exception {
                return super.authenticationManagerBean();
            }


            @Bean
            public TokenStore tokenStore() {
                return new InMemoryTokenStore();
            }

            @Bean
            @Autowired
            public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
                TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
                handler.setTokenStore(tokenStore);
                handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
                handler.setClientDetailsService(clientDetailsService);
                return handler;
            }

            @Bean
            @Autowired
            public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
                TokenApprovalStore store = new TokenApprovalStore();
                store.setTokenStore(tokenStore);
                return store;
            }

        }

MethodSecurityConfig Class:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    @SuppressWarnings("unused")
    @Autowired
    private SecurityConfig securityConfig;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }
}

WebAppInitializer Class:

public class WebAppInitializer implements WebApplicationInitializer{

    @Override
    public void onStartup(ServletContext container) throws ServletException {

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(WebConfig.class);

        container.addListener(new ContextLoaderListener(rootContext));

        DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");

        DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);

        ServletRegistration.Dynamic registration = container.addServlet("dispatcherServlet", dispatcherServlet);

        container.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null,  false, "/*");

        registration.setLoadOnStartup(1);
        registration.addMapping("/");

    }
}

SimpleCorsFilter Class:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {

    public SimpleCorsFilter() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

If I include a second WebSecurityConfig class I do get a 403 code instead.

MyWebSecurity Class:

@Configuration
@EnableWebSecurity
@Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
          .authorizeRequests()
          .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
   }
}

Any suggestions as to how to get past the pre-flight issue beyond waiting for the DATAREST-573 bug to be fixed?

UPDATE: I tried Benjamin's suggestion, but not sure I implemented the solution properly. I have edited my WebConfig class as folows but the pre-flight still fails. The filter still isn't getting applied.

package eng.lab.config;


import javax.servlet.Filter;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@EnableWebMvc
@Configuration
@ComponentScan(basePackages={"eng.lab"})
@Import({ApplicationContext.class})
@PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{

    //more custome rule beans

    @Bean
    public FilterRegistrationBean simpleCORSFilterRegistration() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SimpleCorsFilter());
        registration.addUrlPatterns("/*");
        //registration.addInitParameter("paramName", "paramValue");
        registration.setName("simpleCorsFilter");
        registration.setOrder(0);
        return registration;
    } 

    @Bean(name = "simpleCorsFilter")
    public Filter simpleCorsFilter() {
        return new SimpleCorsFilter();
    }

/*  @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }*/

}
Tony B.
  • 91
  • 4
  • 14

1 Answers1

0

Your CORS filter is not registered. As this is a standard javax.servlet.Filter instance, you need to register it against a FilterRegistrationBean or declare it in your web.xml file.

You can refer to this SO question for more details: How to add a filter class in Spring Boot?

Community
  • 1
  • 1
benjamin.d
  • 2,599
  • 3
  • 15
  • 33