AngularJS $Http CORS with backend in Spring Rest & Security

I have a problem with AngularJS. When I call a Rest Services from another domain, the authorization Header is not sending on the Request, so the Spring Security is not recognizing the authentication credentials. Attached the configuration files.

web.xml

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>

<filter>
<filter-name>cors</filter-name>
<filter-class>com.axcessfinancial.web.filter.CorsFilter</filter-class>

<filter-mapping><filter-name>cors</filter-name><url-pattern>/*</url-pattern></filter-mapping>

Context-Security.xml

<http use-expressions="true">
    <intercept-url pattern="/**" access="isAuthenticated()" />
    <http-basic/>   
</http>

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="admin" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>

CorsFilter

protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    response.addHeader("Access-Control-Allow-Origin", "*");
    if (request.getHeader("Access-Control-Request-Method") != null  && "OPTIONS".equals(request.getMethod())) {
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        response.addHeader("Access-Control-Allow-Headers", "Authorization, Accept, Content-Type, X-PINGOTHER");
        response.addHeader("Access-Control-Max-Age", "1728000");
    }
    filterChain.doFilter(request, response);
}

app.js

var app = angular.module('app', ['app.controller','app.services']);
app.config(function($httpProvider) {    
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];  
    /* $httpProvider.defaults.headers.common['Authorization'] = 'Basic YWRtaW46YWRtaW4='; */
});

service.js

angular.module('app.services',[]).service('Service', function ($http,$q,UtilHttp) {
    $http.defaults.headers.common = {"Access-Control-Request-Headers": "accept, origin, authorization"}; 
    $http.defaults.headers.common['Authorization'] = 'Basic YWRtaW46YWRtaW4=';

    return {
        listCutomer:  function(){
            var defer=$q.defer();
            $http.post('http://localhost:8088/rest-template/soa/listCustomer',{withCredentials: true})
            .success(function(data){
                defer.resolve(data);
            })
            .error(function(data){
                defer.reject(data);
            });
            return defer.promise;
        }
    };
});

Problem:

Response Headersview source
Content-Length  1134
Content-Type    text/html;charset=utf-8
Date    Wed, 21 May 2014 14:39:44 GMT
Server  Apache-Coyote/1.1
Set-Cookie  JSESSIONID=5CD90453C2CD57CE111F45B0FBCB0301; Path=/rest-template
WWW-Authenticate    Basic realm="Spring Security Application"
Request Headers
Accept  text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Access-Control-Request-He...    authorization,content-type
Access-Control-Request-Me...    POST
Cache-Control   no-cache
Connection  keep-alive
Host    localhost:8088
Origin  null
Pragma  no-cache
User-Agent  Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0

I think your problem is the following:

when

  • use HTTP verb other than GET or POST
  • need Custom headers to be sent (e.g. Authentication, X-API-Key etc)
  • need The request body has a MIME type other than text/plain

your browser (following CORS specs) adds an extra step to the request:

it first sends a particular request with method "OPTIONS" to the URL, if the server respond approving the actual request you want your real request will start.

Unfortunately in your scenario spring returns 401 (unauthorized) to the OPTIONS request because auth token is not present in this request, consequently your real request will never start

Solution:

you can put your cors filter before the spring security filter in your web.xml and avoid calling next filter in the chain (spring security) if request method is OPTIONS

this exaple filter works for me:

public class SimpleCORSFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    response.setHeader("Access-Control-Allow-Credentials", "true");
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");

    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);
    } else {
        chain.doFilter(req, res);
    }

}

public void init(FilterConfig filterConfig) {
}

public void destroy() {
}

}

remember to declare your cors filter before the spring security filter in your web.xml

Add your filter to the front of the Spring Security filter chain...

<http>
   <custom-filter before="CHANNEL_FILTER" ref="myFilter" />
 </http> 

<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

see http://docs.spring.io/spring-security/site/docs/3.0.x/reference/ns-config.html