Spring Security login with Java and Angular2
angular, ionic2, java, spring, spring-security
Solution
This seems to be problem related to CORS configuration. Browser does make the OPTION preflight request before your actual GET call. I don't have much knowledge in this area, but you could try adding the filter below to the spring back end.
public class CORSFilter extends GenericFilterBean {
public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse response = (HttpServletResponse) res;
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.addHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token");
response.addHeader("Access-Control-Allow-Credentials", "true");
if(req.getMethod().equalsIgnoreCase("options")){
return;
}
chain.doFilter(request, response);
}
}
And add the line below in the configure method of your WebSecurityConfig.
`.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);`
Problem
I am having some trouble integrating spring security (namely the login part) in my web application. My BackEnd is running on localhost:8080 and the FrontEnd (Ionic 2 with AngularJS 2) is running on localhost:8100. I managed to log in (at least I think so) but the rest of the requests become unavailable and I get the following error in the Chrome Developer Console: ``` XMLHttpRequest cannot load http://localhost:8080/company/getAll. Response for preflight is invalid (redirect) ``` When testing with Postman it seems to be working, I log in and then I am able to perform POST requests on http://localhost:8080/company/getAll and everything works as intended. Presumably I'm missing something (like a token) but can't figure out what. I am new to both Ionic and Spring Security so please forgive me if this is something trivial. I tried googling various tutorials but was unable to find anything (most of them were using JSP). How could I get this to work? How should my requests from the frontend look like? Here is my login method from the BackEnd controller: ``` @RequestMapping(value = "/login", method = RequestMethod.GET) @CrossOrigin(origins = "*") public ResponseEntity<GeneralResponse> login(Model model, String error, String logout) { System.out.println("LOGIN"+model.toString()); if (error != null) { System.out.println("1.IF"); model.addAttribute("error", "Your username and password is invalid."); } if (logout != null) { System.out.println("2.IF"); model.addAttribute("message", "You have been logged out successfully."); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new GeneralResponse(true, "FAILURE: Error")); } ``` This is my WebSecurityConfig: ``` @Configuration @EnableWebSecurity @ComponentScan("com.SAB.service") public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**", "/registration").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/user/login") .permitAll() .and() .logout() .permitAll(); http .csrf().disable(); http.formLogin().defaultSuccessUrl("http://localhost:8100/", true); //.and().exceptionHandling().accessDeniedPage("/403") //.and().sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } } ``` And finally my FrontEnd code responsible for logging in: ``` login(){ var body = 'username='+this.loginForm.controls['username'].value+'&password='+this.loginForm.controls['password'].value; var headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); //headers.append("Access-Control-Allow-Origin", "*"); this.http .post('http://localhost:8080/user/login', body, { headers: headers }) .subscribe(data => { console.log('ok'); console.log(data) }, error => { console.log(JSON.stringify(error.json())); }); } ``` If you require any additional details please let me know and I will provide them. Thanks in advance! I don't get the Access-COntroll-Origin error but I tried to change it according to the comments anyway and now I get a "Invalid CORS request" Request and Response Header for login from postman: EDIT: Here are the Spring Security logs. However Spring only registers a OPTIONS request and no POST even though the FrontEnd is calling POST. ``` ************************************************************ Request received for OPTIONS '/login': org.apache.catalina.connector.RequestFacade@18859793 servletPath:/login pathInfo:null headers: host: localhost:8080 connection: keep-alive access-control-request-method: POST origin: http://localhost:8100 user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36 access-control-request-headers: access-control-allow-origin accept: */* referer: http://localhost:8100/ accept-encoding: gzip, deflate, sdch, br accept-language: en-US,en;q=0.8 Security filter chain: [ WebAsyncManagerIntegrationFilter SecurityContextPersistenceFilter HeaderWriterFilter CorsFilter LogoutFilter UsernamePasswordAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor ] ************************************************************ 2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /login at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' 2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /login at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' 2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists 2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created. 2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /login at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /login at position 4 of 12 in additional filter chain; firing Filter: 'CorsFilter' 2017-05-12 19:18:28.541 DEBUG 16500 --- [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@5baaaa2b 2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession. 2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed ``` UPDATE: So after adding the CORSFilter suggested in the accepted answer I also had to modify my requests in the frontend in order for the cookie with the JSESSIONID to be sent on each request. Bassically I had to add the following to ALL my reqquests: `let options = new RequestOptions({ headers: headers, withCredentials: true });`