Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security login with Java and Angular2

Tags:

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

like image 764
Irina Avram Avatar asked May 09 '17 16:05

Irina Avram


People also ask

How do I enable Spring Security in Java application?

To enable Spring security in Java Web application, you need to configure three things - declare a delegating proxy filter in web. xml, add the ContextLoaderListener in web. xml and provide actual security constraints on applicationContext-Security. xml file.

How do I set Spring Security username and password?

Method 1: Changing in the application properties file Now go to any browser and type localhost:8080 and try to access any local API we cannot access the API first we have to bypass the security. The user name and password are the same as we mention in the application. properties file.


2 Answers

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

like image 103
Vipul Goyal Avatar answered Oct 30 '22 15:10

Vipul Goyal


The solution is very simple. The username and password on POST needs to be submitted as FormData and not part of body. I had the same issue with my app and after a long struggle I created a HTML form and did a POST then it worked like a charm.

See below live example. You should be able to signup with any id and try to login and debut it in your browser. It uses Spring boot for server side and Angular 4 on UI.

Live Demo: http://shop-venkatvp.rhcloud.com/#/

Login HTML: https://github.com/reflexdemon/shop/blob/master/src/app/login/login.component.html#L7-L31

Spring Security Configuration: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/WebSecurityConfig.java#L20-L34

Login controller: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/page/controller/LoginController.java

Hope this is helpful.

like image 44
reflexdemon Avatar answered Oct 30 '22 15:10

reflexdemon