Question: Spring Security AJAX login with CORS and CSRF

Question

Spring Security AJAX login with CORS and CSRF

Answers 0
Added at 2017-01-05 19:01
Tags
Question

I have problem with AJAX login on another server with Spring Security and CSRF tokens.

I have very little time before production, app is working very slow with bigger number of users, and have a few not so great server instances. I've been planing on solving this problem with spliting server work on AJAX and non-AJAX server calls. If there is other way around please help me, also every advice is very welcome. My solution is below.

I need to have one Main Server and multiple API servers. They are basically same applications, but created like that so that all AJAX calls go to different API servers, and Main server should work with JSP pages. And now I have problem with logging in on API server.

I have created next responsibility chain.

  1. AJAX GET Request to /api/csrf - Returns CSRF in header
  2. AJAX POST Request to /ajax/login - Should login to API server

CORS is working properly it always returns 200 OPTIONS response before POST login request witch returns 405 Method not allowed, I assume problem is in CSRF tokens.

AJAX looks like this

function loginUser(){

// retrieves CSRF tokens and then calls for login
$.ajax({
    url: "http://localhost:8081/gabe-is-cool-guy/api/csrf",
    type: "GET", 
    crossDomain: true,
    success: function(data, status, request){

        // CSRF parameters
        var header = request.getResponseHeader("X-CSRF-HEADER");
        var param = request.getResponseHeader("X-CSRF-PARAM");
        var token = request.getResponseHeader("X-CSRF-TOKEN");

        // User credentials
        var username = $("#username").val();
        var password = $("#passwrod").val();
        var data = {
            "j_username" : username,
            "j_password" : password
        };

        // login user
        $.ajax({
            url: "http://localhost:8081/gabe-is-cool-guy/ajax/login",
            type: "POST", 
            crossDomain: true,
            data: JSON.stringify(data),
            beforeSend : function(xhr) {
                xhr.setRequestHeader(header, token);
                xhr.setRequestHeader("Accept", "application/json");
                xhr.setRequestHeader("Content-Type", "application/json");
            },
            success : function(data) {
                console.log("Success");

                if(data.status)
                    console.log("Login successful");
                else
                    console.log("Bad credentialas")
            }
        });
    }
});
}

CSRFController.java

@Controller
@RequestMapping("/api/csrf")
public class CSRFController {

    @CrossOrigin(exposedHeaders={"X-CSRF-HEADER","X-CSRF-PARAM","X-CSRF-TOKEN"})
    @ResponseBody
    @GetMapping
    public String getToken(HttpServletRequest request, HttpServletResponse response){

        CsrfToken token = (CsrfToken) request.getAttribute("_csrf");

        // Spring Security will allow the Token to be included in this header name
        response.setHeader("X-CSRF-HEADER", token.getHeaderName());

        // Spring Security will allow the token to be included in this parameter name
        response.setHeader("X-CSRF-PARAM", token.getParameterName());

        // this is the value of the token to be included as either a header or an HTTP parameter
        response.setHeader("X-CSRF-TOKEN", token.getToken());

        return "Status OK";
    }
}

LoginController.java (Works on same server)

@CrossOrigin
@Controller
@RequestMapping("/ajax/login")
public class LoginController {

    @Autowired
    @Qualifier("authenticationManager")
    AuthenticationManager authenticationManager;

    @Autowired
    SecurityContextRepository repository;

    @Autowired
    RememberMeServices rememberMeServices;

    @ResponseBody
    @RequestMapping(method = RequestMethod.POST,produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
    public String performLogin(@RequestBody JUserWrapper user, HttpServletRequest request, HttpServletResponse response) {

        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getJ_username(), user.getJ_password());

        try {
            Authentication auth = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(auth);

            repository.saveContext(SecurityContextHolder.getContext(), request, response);
            rememberMeServices.loginSuccess(request, response, auth);

            return "{\"status\": true}";

        } catch (BadCredentialsException ex) {
            return "{\"status\": false, \"error\": \"Bad Credentials\"}";
        }
    }
}

APICorsFilter.java

public class APICorsFilter extends CorsFilter {

    public APICorsFilter() {
        super(configurationSource());
    }

    private static UrlBasedCorsConfigurationSource configurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:8080");
        config.addAllowedOrigin("http://localhost:8081");
        config.addAllowedOrigin("http://localhost:8082");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}
Answers to

Spring Security AJAX login with CORS and CSRF

Source Show
◀ Wstecz