10

I got an issue related to the HTTP response header "Access-Control-Allow-Origin" when using basic authetication with Spring. When I authenticate manually, like the code bellow (I'm using REST):

@RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") @ResponseStatus(value = HttpStatus.OK) public void login(@RequestBody String body, HttpServletResponse response) throws IOException { try { User user = gson.fromJson(body, User.class); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( usuario.getUsername(), usuario.getPassword()); authenticationManager.authenticate(token); } catch (BadCredentialsException e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } catch (Exception e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } 

everything works fine, I receive the following HTTP response:

HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Access-Control-Allow-Origin: null Access-Control-Allow-Credentials: true Content-Type: text/html;charset=utf-8 Content-Length: 951 Date: Fri, 17 May 2013 19:14:36 GMT 

as you can see, "Access-Control-Allow-Origin" is present on the response. Everything is fine here. I can catch a 401 error in my ajax call.

But when the authentication is performed automatically, like the code bellow:

@RequestMapping(value = "/name", method = RequestMethod.POST, consumes = "application/json") @PreAuthorize("hasRole('ROLE_CUSTOMER')") public @ResponseBody String getName(HttpServletResponse response) throws IOException { String json = null; try { User userSession = (User) SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); Customer customer = customerDao.getNameByUsername(userSession.getUsername()); json = gson.toJson(customer); } catch (Exception e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } return json; } 

the HTTP response is:

HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 WWW-Authenticate: Basic realm="Spring Security Application" Content-Type: text/html;charset=utf-8 Content-Length: 981 Date: Fri, 17 May 2013 19:41:08 GMT 

There is no "Access-Control-Allow-Origin" in the response

Google Chrome console show the following error:

Origin null is not allowed by Access-Control-Allow-Origin 

My ajax call does not return a 401 Unauthorized error, even though the HTTP response return it (response above), I receive an unknow error.

I figured out that for all browsers, I need a "Access-Control-Allow-Origin" in the HTTP response, otherwise they will generate some kind of silent error and my ajax call will fail (can't catch the 401 error). Actually, javascript will fail silently. XMLHttpRequest does not accept an HTTP response without "Access-Control-Allow-Origin".

How can I make Spring inject this "Access-Control-Allow-Origin" in HTTP responses for basic authentication?

this is my Spring Security xml:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <security:http create-session="stateless" entry-point-ref="authenticationEntryPoint"> <security:intercept-url pattern="/customer/**" /> <security:http-basic /> <security:custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" /> </security:http> <bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager" /> <property name="authenticationEntryPoint" ref="authenticationEntryPoint" /> </bean> <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> <property name="realmName" value="teste.com" /> </bean> <!-- It is responsible for validating the user's credentials --> <security:authentication-manager alias="authenticationManager"> <!-- It is responsible for providing credential validation to the AuthenticationManager --> <security:authentication-provider> <security:password-encoder ref="passwordEncoder" /> <security:jdbc-user-service data-source-ref="mySQLdataSource" users-by-username-query="select username, password, enabled from usuario where username = ?" authorities-by-username-query="select username, papel from autoridade where username = ?" /> </security:authentication-provider> </security:authentication-manager> <bean class="org.springframework.security.crypto.password.StandardPasswordEncoder" id="passwordEncoder" /> </beans> 

1 Answer 1

14

Just found my own way:

First of all, I don't really remember why I put this line here, but it was messing up my code:

<security:http-basic /> 

Second, this answer show me the path: Handle unauthorized error message for Basic Authentication in Spring Security. I had to create a custom authentication entry point in order to send the Access-Control-Allow-Origin thing.

So this is my code now:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <security:http create-session="stateless" entry-point-ref="authenticationEntryPoint"> <security:intercept-url pattern="/api/admin/**" /> <security:intercept-url pattern="/medico/**" /> <!-- <security:http-basic /> --> <security:custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" /> </security:http> <bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager" /> <property name="authenticationEntryPoint" ref="authenticationEntryPoint" /> </bean> <!-- <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> <property name="realmName" value="test.com" /> </bean> --> <bean id="authenticationEntryPoint" class="com.test.util.PlainTextBasicAuthenticationEntryPoint"> <property name="realmName" value="test.com" /> </bean> <!-- It is responsible for validating the user's credentials --> <security:authentication-manager alias="authenticationManager"> <!-- It is responsible for providing credential validation to the AuthenticationManager --> <security:authentication-provider> <security:password-encoder ref="passwordEncoder" /> <security:jdbc-user-service data-source-ref="mySQLdataSource" users-by-username-query="select username, password, enabled from usuario where username = ?" authorities-by-username-query="select username, papel from autoridade where username = ?" /> </security:authentication-provider> </security:authentication-manager> <bean class="org.springframework.security.crypto.password.StandardPasswordEncoder" id="passwordEncoder" /> </beans> 
package com.test.util; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; public class PlainTextBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.addHeader("Access-Control-Allow-Origin", "null"); response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\""); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter writer = response.getWriter(); writer.println("HTTP Status " + HttpServletResponse.SC_UNAUTHORIZED + " - " + authException.getMessage()); } } 

My http response now:

HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Access-Control-Allow-Origin: null WWW-Authenticate: Basic realm="test.com" Content-Length: 35 Date: Mon, 20 May 2013 20:05:03 GMT HTTP Status 401 - Bad credentials 

before the alteration, I got this error message:

OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2 XMLHttpRequest cannot load http://localhost:8080/test/customer/name. Origin null is not allowed by Access-Control-Allow-Origin. 

and now as expected I get this one:

OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2 POST http://localhost:8080/test/customer/name 401 (Unauthorized) 
Sign up to request clarification or add additional context in comments.

2 Comments

Hi, I would like to ask you the following. I'd like to know if there is something specific to enable CROSS Request + Basic Authorization + Spring Security. So far i have done everything necessary to set the Tomcat Filter (I added Authorization to the headers), I do my request in ajax as it should, given that it works without authentification, so i wonder if the spring security is not causing an issue ..... Maybe you have some input about it
Access-Control-Allow-Origin is the front-end address. You could set "*" to allow all.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.