Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security, REST basic authentication issue

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>
like image 635
raonirenosto Avatar asked May 17 '13 20:05

raonirenosto


1 Answers

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) 
like image 59
raonirenosto Avatar answered Oct 09 '22 12:10

raonirenosto