0

I’m having a configuration problem with Spring Security re-directing to a login page on my React front end (running with React Router) when running in development mode. I believe the redirect isn't being picked up by my front end, and is due to my noob understanding of how server side routing interacts with SPA routing.

My development front end web server (using Facebook Create React App) is running on localhost:3000, my backend Spring Boot Tomcat on localhost:8080. I’ve enabled Spring Security, which works fine on the Tomcat server (port 8080); accessing localhost:8080 forwards to localhost:8080/login. When accessing localhost:3000, I got a cross-origin error, so added CORS support in Spring as described here. I also consulted this Spring blog post. It seems to work, in that I no longer get the cross-origin error. However, while not logged in, the request from localhost:3000 to localhost:8080/login returns the following:

HTTP/1.1 200 OK
X-Powered-By: Express
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=UTF-8
Content-Length: 1496
ETag: W/"5d8-80rXYG+kNfQ/xEJ7J2f1PA"
Vary: Accept-Encoding
Date: Tue, 10 Jan 2017 12:04:07 GMT
Connection: keep-alive

<!DOCTYPE html>
...

This is the index.html hosted in my front end webserver. This is good, but I need it to forward to localhost:3000/login, which will render the login page into index.html.

I believe it's not forwarding to /login because of the routing setup, it goes from localhost:3000/ -> HttpSecurity.loginPage("/login") (Spring) -> registry.addViewController("/login").setViewName("forward:/index.html") (Spring) -> <Route path="/" component={ForwardToLandingPage} /> (React Router)

It goes wrong at the end. I want the Tomcat server to re-direct the front end to :3000/login, but instead the front end goes to the component. Seems there's a problem between the server side redirect and the front end React Router going to the right place. If this is too complex to set up, I might just drop this whole Spring Security in my development environment.

Here are my configurations:

React routing config

ReactDOM.render((
    <Router history={browserHistory}>
        <Route path="/" component={ForwardToLandingPage} />
        <Route path="/login" component={Login} />
    </Router>
    ),
    document.getElementById('root')
);

Spring MVC config

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {          
        // This forwarding is required, as my React app is rendering on a div
        // element with the ID 'root' in /index.html. But is this part of the
        // problem?
        registry.addViewController("/").setViewName("forward:/index.html");
        registry.addViewController("/login").setViewName("forward:/index.html");
    }
}

Spring CORS config

@Configuration
public class MyConfiguration {

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

Spring Security config

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // temporary fix
                .authorizeRequests()
                    .antMatchers("/h2-console", "/public/**", "/static/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
    }
}

Any ideas what's going wrong?

I was wondering if the solution to the problem should involve Tomcat, so checked this too.

Thanks for any help! :)

Community
  • 1
  • 1
Jannik
  • 357
  • 2
  • 7
  • 15
  • Could you post a link to the Facebook Create React App, Does it make ajax requests, or regular http requests? – Klaus Groenbaek Jan 09 '17 at 19:19
  • `localhost:8080/login` is that the login page? – chaoluo Jan 10 '17 at 04:42
  • Thanks for your questions. I've been experimenting more today, I now believe the problem has more to do with routing and redirection between my backend and front end. I've updated my question with my findings and configs. Also I should add that the problem I'm having is when I'm on my development environment. When going straight to port 8080 or on hosted AWS Elastic Beanstalk, serving all (including front end) content works fine. If it's too complex to fix, I'll just disable login in the development environment. – Jannik Jan 10 '17 at 13:44
  • @dur Good question, I do. Your question helped me narrow down the problem! :) The response body is the index.html hosted on front end React web server (Express on port 3000). I've added the response to my question. Please see the question regarding my thoughts. – Jannik Jan 10 '17 at 13:45
  • @KlausGroenbaek The login form on /login makes is a regular HTTP POST. But the problem is I’m not even getting to this form. I've added the React routing code to my question, which should help. – Jannik Jan 10 '17 at 13:47
  • @chaoluo That is the login page when accessing via the Tomcat server on port 8080. But I also want to be able to reach it from port 3000. I'm on port 3000 when developing the app. For production, I bundle the front end app and serve via Tomcat. – Jannik Jan 10 '17 at 13:47
  • hi @Jannik did you solve this problem? – imdzeeshan Jul 18 '17 at 14:24
  • @imdzeeshan No, unfortunately not. So in my dev environment, I manually go to port 8080 to login, and then back again. – Jannik Sep 15 '17 at 11:58

1 Answers1

-1

We are using an older version of Spring security, and I don't know if it supports CORS or not. I have used Spring security for 6 years, so I just created a regular filter:

public class CORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest httpServletRequest = (HttpServletRequest) req;
        // The headers needs to be on all responses.
        String origin = httpServletRequest.getHeader("origin");
        if (origin == null) {
            // Make the developers life simpler. The browser adds the origin but it is a pain to
            // for developers to add this header while testing.
            origin = "*";
        }
        response.setHeader("Access-Control-Allow-Origin", origin.replace("\n", ""));

        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept, Authorization");

        // For options requests return immediately. Options does not require authentication so we want to return
        // here to avoid all yet unknown security risks.
        if (HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())) {
            return;
        }
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {
    }

    public void destroy() {
    }
}

And added before the ChannelProcessingFilter, so it becomes the first filter in the filter chain.

 http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
 http.formLogin().....
// all the other matchers and 

If this does not work for you, then I'm not sure it is only a CORS issue.

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • 1
    Thanks for this Klaus. :) Just added it, it works fine, as my CORS filter does. I thought CORS was somehow breaking redirection to the login page. I've now determined the problem is only with the routing and redirection, CORS is working fine. – Jannik Jan 10 '17 at 15:09