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>
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With