Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security Ajax login

I have implemented this security proccess in my project: Spring Security 3 - MVC Integration Tutorial (Part 2).

My problem is that I need to turn it into an Ajax-based login. What do I need to do in order to make this XML suitable with just returning string/JSON to the client? I understand that the problem might be in the form-login tag.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    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.0.xsd
                           http://www.springframework.org/schema/security 
                           http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <!-- This is where we configure Spring-Security  -->
    <http auto-config="true" use-expressions="true" access-denied-page="/Management/auth/denied" >

        <intercept-url pattern="/Management/auth/login" access="permitAll"/>
        <intercept-url pattern="/Management/main/admin" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/Management/main/common" access="hasRole('ROLE_USER')"/>

        <form-login 
                login-page="/Management/auth/login" 
                authentication-failure-url="/Management/auth/login?error=true" 
                default-target-url="/Management/main/common"/>

        <logout 
                invalidate-session="true" 
                logout-success-url="/Management/auth/login" 
                logout-url="/Management/auth/logout"/>

    </http>

    <!-- Declare an authentication-manager to use a custom userDetailsService -->
    <authentication-manager>
            <authentication-provider user-service-ref="customUserDetailsService">
                    <password-encoder ref="passwordEncoder"/>
            </authentication-provider>
    </authentication-manager>

    <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
    <beans:bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

    <!-- A custom service where Spring will retrieve users and their corresponding access levels  -->
    <beans:bean id="customUserDetailsService" class="com.affiliates.service.CustomUserDetailsService"/>

</beans:beans>
like image 921
MushMushon Avatar asked Feb 06 '11 09:02

MushMushon


1 Answers

This is an old post, but it still comes up as one of the top results for "spring security ajax login," so I figured I'd share my solution. It follows Spring Security standards and is pretty simple to setup, the trick is to have 2 <http> elements in your security configuration, one for REST/Ajax and one for the rest of the app (regular HTML pages). The order in which <http>'s appear is important, it has to go from more specific to more generic URLs, just like <url-intercept> elements inside of a <http>.

Step 1: Setup Two Separate <http>'s

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:beans="http://www.springframework.org/schema/beans" 
    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.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <!-- a shared request cache is required for multiple http elements -->
    <beans:bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache" />

    <!-- remove security from static resources to avoid going through the security filter chain -->
    <http pattern="/resources/**" security="none" />

    <!-- http config for REST services (AJAX interface) 
    =================================================== -->
    <http auto-config="true" use-expressions="true" pattern="/rest/**">
        <!-- login configuration 
            login-processing-url="/rest/security/login-processing" front-end AJAX requests for authentication POST to this URL
            login-page="/rest/security/login-page" means "authentication is required"
            authentication-failure-url="/rest/security/authentication-failure" means "authentication failed, bad credentials or other security exception"
            default-target-url="/rest/security/default-target" front-end AJAX requests are redirected here after success authentication
        -->
        <form-login 
            login-processing-url="/rest/security/login-processing" 
            login-page="/rest/security/login-page" 
            authentication-failure-url="/rest/security/authentication-failure" 
            default-target-url="/rest/security/default-target" 
            always-use-default-target="true" />
        <logout logout-url="/rest/security/logout-url" />

        <!-- REST services can be secured here, will respond with JSON instead of HTML -->
        <intercept-url pattern="/rest/calendar/**" access="hasRole('ROLE_USER')" />

        <!-- other REST intercept-urls go here -->

        <!-- end it with a catch all -->
        <intercept-url pattern="/rest/**" access="isAuthenticated()" />

        <!-- reference to the shared request cache -->
        <request-cache ref="requestCache"/>
    </http>

    <!-- http config for regular HTML pages
    =================================================== -->
    <http auto-config="true" use-expressions="true">
        <form-login 
            login-processing-url="/security/j_spring_security_check" 
            login-page="/login" 
            authentication-failure-url="/login?login_error=t" />
        <logout logout-url="/security/j_spring_security_logout" />

        <intercept-url pattern="/calendar/**" access="hasRole('ROLE_USER')" />

        <!-- other intercept-urls go here -->

        <!-- in my app's case, the HTML config ends with permitting all users and requiring HTTPS
             it is always a good idea to send sensitive information like passwords over HTTPS -->
        <intercept-url pattern="/**" access="permitAll" requires-channel="https" />

        <!-- reference to the shared request cache -->
        <request-cache ref="requestCache"/>
    </http>

    <!-- authentication manager and other configuration go below -->
</beans:beans>

Step 2: REST Authentication Controller

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import flexjson.JSONSerializer;

@Controller
@RequestMapping(value = "/rest/security")
public class RestAuthenticationController {

    public HttpHeaders getJsonHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        return headers;
    }

    @RequestMapping(value="/login-page", method = RequestMethod.GET)
    public ResponseEntity<String> apiLoginPage() {
        return new ResponseEntity<String>(getJsonHeaders(), HttpStatus.UNAUTHORIZED);
    }

    @RequestMapping(value="/authentication-failure", method = RequestMethod.GET)
    public ResponseEntity<String> apiAuthenticationFailure() {
        // return HttpStatus.OK to let your front-end know the request completed (no 401, it will cause you to go back to login again, loops, not good)
        // include some message code to indicate unsuccessful login
        return new ResponseEntity<String>("{\"success\" : false, \"message\" : \"authentication-failure\"}", getJsonHeaders(), HttpStatus.OK);
    }

    @RequestMapping(value="/default-target", method = RequestMethod.GET)
    public ResponseEntity<String> apiDefaultTarget() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // exclude/include whatever fields you need
        String userJson = new JSONSerializer().exclude("*.class", "*.password").serialize(authentication);
        return new ResponseEntity<String>(userJson, getJsonHeaders(), HttpStatus.OK);
    }
}

Step 3: Submit AJAX form and process the response, required jQuery's ajaxForm library

<form action="/rest/security/login-processing" method="POST">
...
</form>

$('form').ajaxForm({
    success: function(response, statusText, xhr, $form)  {
        console.log(response);
        if(response == null || response.username == null) {
            alert("authentication failure");
        } else {
            // response is JSON version of the Spring's Authentication
            alert("authentication success");
        }
    },
    error: function(response, statusText, error, $form)  { 
        if(response != null && response.message == "authentication-failure") {
            alert("authentication failure");
        }
    }
});
like image 168
SergeyB Avatar answered Oct 20 '22 01:10

SergeyB