Cross-Origin Resource Sharing with Spring Security

I'm trying to make CORS play nicely with Spring Security but it's not complying. I made the changes described in this article and changing this line in applicationContext-security.xml has got POST and GET requests working for my app (temporarily exposes controller methods, so I can test CORS):

  • Before: <intercept-url pattern="/**" access="isAuthenticated()" />
  • After: <intercept-url pattern="/**" access="permitAll" />

Unfortunately the following URL which allows Spring Security logins through AJAX isn't responding: http://localhost:8080/mutopia-server/resources/j_spring_security_check. I am making the AJAX request from http://localhost:80 to http://localhost:8080.

In Chrome

When attempting to access j_spring_security_check I get (pending) in Chrome for the OPTIONS preflight request and AJAX call returns with HTTP status code 0 and message "error".

In Firefox

The preflight succeeds with HTTP status code 302 and I still get the error callback for my AJAX request directly afterwards with HTTP status 0 and message "error".

enter image description here

enter image description here

AJAX Request Code

function get(url, json) {
    var args = {
        type: 'GET',
        url: url,
        // async: false,
        // crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        success: function(response) {
            console.debug(url, response);
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
        }
    };
    if (json) {
        args.contentType = 'application/json'
    }
    $.ajax(args);
}

function post(url, json, data, dataEncode) {
    var args = {
        type: 'POST',
        url: url,
        // async: false,
        crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        beforeSend: function(xhr){
            // This is always added by default
            // Ignoring this prevents preflight - but expects browser to follow 302 location change
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader("X-Ajax-call", "true");
        },
        success: function(data, textStatus, xhr) {
            // var location = xhr.getResponseHeader('Location');
            console.error('success', url, xhr.getAllResponseHeaders());
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
            console.error('fail', url, xhr.getAllResponseHeaders());
        }
    }
    if (json) {
        args.contentType = 'application/json'
    }
    if (typeof data != 'undefined') {
        // Send JSON raw in the body
        args.data = dataEncode ? JSON.stringify(data) : data;
    }
    console.debug('args', args);
    $.ajax(args);
}

var loginJSON = {"j_username": "username", "j_password": "password"};

// Fails
post('http://localhost:8080/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
post('http://localhost/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
get('http://localhost:8080/mutopia-server/landuses?projectId=6', true);

// Works
post('http://localhost:8080/mutopia-server/params', true, {
    "name": "testing",
    "local": false,
    "generated": false,
    "project": 6
}, true);

Please note - I can POST to any other URL in my app via CORS except the Spring Security login. I've gone through lots of articles, so any insight into this strange issue would be greatly appreciated


I was able to do this by extending UsernamePasswordAuthenticationFilter... my code is in Groovy, hope that's OK:

public class CorsAwareAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    static final String ORIGIN = 'Origin'

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
        if (request.getHeader(ORIGIN)) {
            String origin = request.getHeader(ORIGIN)
            response.addHeader('Access-Control-Allow-Origin', origin)
            response.addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
            response.addHeader('Access-Control-Allow-Credentials', 'true')
            response.addHeader('Access-Control-Allow-Headers',
                    request.getHeader('Access-Control-Request-Headers'))
        }
        if (request.method == 'OPTIONS') {
            response.writer.print('OK')
            response.writer.flush()
            return
        }
        return super.attemptAuthentication(request, response)
    }
}

The important bits above:

  • Only add CORS headers to response if CORS request detected
  • Respond to pre-flight OPTIONS request with a simple non-empty 200 response, which also contains the CORS headers.

You need to declare this bean in your Spring configuration. There are many articles showing how to do this so I won't copy that here.

In my own implementation I use an origin domain whitelist as I am allowing CORS for internal developer access only. The above is a simplified version of what I am doing so may need tweaking but this should give you the general idea.


well This is my code working very well and perfect for me: I spent two days working on it and understanding spring security so I hope you accept it as the answer, lol

 public class CorsFilter extends OncePerRequestFilter  {
    static final String ORIGIN = "Origin";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        System.out.println(request.getHeader(ORIGIN));
        System.out.println(request.getMethod());
        if (request.getHeader(ORIGIN).equals("null")) {
            String origin = request.getHeader(ORIGIN);
            response.setHeader("Access-Control-Allow-Origin", "*");//* or origin as u prefer
            response.setHeader("Access-Control-Allow-Credentials", "true");
           response.setHeader("Access-Control-Allow-Headers",
                    request.getHeader("Access-Control-Request-Headers"));
        }
        if (request.getMethod().equals("OPTIONS")) {
            try {
                response.getWriter().print("OK");
                response.getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{
        filterChain.doFilter(request, response);
        }
    }
}

well then you need to also set your filter to be invoked:

<security:http use-expressions="true" .... >
     ...
     //your other configs
    <security:custom-filter ref="corsHandler" after="PRE_AUTH_FILTER"/> // this goes to your filter
</security:http>

Well and you need a bean for the custom filter you created:

<bean id="corsHandler" class="mobilebackbone.mesoft.config.CorsFilter" />