Spring Security login with Java and Angular2

Irina Avram :

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: enter image description here

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

Vipul Goyal :

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);

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=429124&siteId=1