Customizing Spring Security Login using ExtJS

This tutorial will walk through the steps to customize Spring Security login page using Ext JS form. There are a few steps need to be done to customize the login form, because Ext JS form does not work in the same way as the standard HTML form. By default Ext Forms are submitted through Ajax and response packets are assumed to be JSON. I will be adding the customize login page into my previous example (in Spring 3 MVC and ExtJS Forms). I will walk through:
  1. configuration required to integrate with Spring Security 3 (3.0.5 to be exact),
  2. customize login page, and
  3. implement login handler to return JSON.
Step 1: Configuration required to integrate with Spring Security. If you are using the Spring MVC project template from STS, you will need to add dependencies to Spring Security by modifying the Maven pom.xml file. Add the following property and dependencies:
<properties>
	...
	<org.springframework.security-version>3.0.5.RELEASE</org.springframework.security-version>
	...
</properties>
<dependencies>
      ...
      <!-- Spring Security -->
      <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${org.springframework.security-version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
      </dependency>
    <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${org.springframework.security-version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
      </dependency>
    <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${org.springframework.security-version}</version>
      </dependency>
    <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${org.springframework.security-version}</version>
      </dependency>
      ...
</dependencies>
Now, create security context file. Let’s name it applicationContext-security.xml. For this tutorial I used in memory user service and declare user id and password as part of the authentication manager. In the configuration, I declared the form login URL and the login handler. The login page is pointing to the login controller which forward to login.jsp. While login handler beans are used to return JSON to Ext JS form. The interceptor defined that every URL must be authenticated except for login and resources (images,css,javascript). Remember to set the access permission of login page to permit all to avoid endless loop. It is a good practice to default all URL to isAuthenticate() and only permit the necessary URL.
<http use-expressions="true">
    <intercept-url pattern="/resources/**" filters="none"/>
    <intercept-url pattern="/app/login.do" access="permitAll()" />
    <intercept-url pattern="/**" access="isAuthenticated()" />
      <form-login login-page="/app/login.do"
      			authentication-success-handler-ref="loginSuccessHandler"
      			authentication-failure-handler-ref="loginFailureHandler" />
      <logout invalidate-session="true" logout-success-url="/" logout-url="/j_spring_security_logout"/>
      <remember-me key="xaab.springmvclogin" />
      <session-management session-fixation-protection="newSession" >
          <concurrency-control max-sessions="1" error-if-maximum-exceeded="false"/>
      </session-management>
</http>
<!-- all password = password -->
<authentication-manager>
     <authentication-provider>
      	<password-encoder hash="md5" />
        <user-service>
            <user name="user1" password="5f4dcc3b5aa765d61d8327deb882cf99" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
            <user name="user2" password="5f4dcc3b5aa765d61d8327deb882cf99" authorities="ROLE_USER,ROLE_TELLER" />
            <user name="user3" password="5f4dcc3b5aa765d61d8327deb882cf99" authorities="ROLE_USER" />
            <user name="user4" password="5f4dcc3b5aa765d61d8327deb882cf99" authorities="ROLE_USER" />
         </user-service>
      </authentication-provider>
</authentication-manager>

<beans:bean id="loginSuccessHandler" class="org.xaab.springmvc.LoginSuccessHandler" />
<beans:bean id="loginFailureHandler" class="org.xaab.springmvc.LoginFailureHandler" />
In order to activate the above configuration, you need to include the security context into the context parameter and include the security filter in web.xml.
<!-- part of web.xml -->
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/spring/root-context.xml
		/WEB-INF/spring/applicationContext-security.xml
	</param-value>
</context-param>

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

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
Step 2: Customize login page. There are basic 3 elements in the default login page generated by Spring Security. They are:
  1. Form action: j_spring_security_check
  2. User name input: j_username
  3. Password input: j_password
Those are the form fields needed for the Ext login form. The UsernamePasswordAuthenticationFilter watches for a request to the virtual URL (/j_spring_security_check) used for form-based authentication, and attempts to authenticate the user. If necessary, You can customize it to use your own controller to authenticate. Take a look at the spring-mvc-login.js below. One thing to note is, this example redirect to a default URL upon successful login. It is done by modifying the window.location.
Ext.onReady(function(){
	Ext.QuickTips.init();

	var loginForm = new Ext.FormPanel({
		url: defLoginUrl,
		title: 'Login',
		renderTo: Ext.getBody(),
		frame: true,
		cls: 'my-form-class',
		width: 350,
		items: [{
			xtype: 'textfield',
			fieldLabel: 'Login',
			name: 'j_username'
	              },{
			xtype: 'textfield',
			inputType: 'password',
			fieldLabel: 'Password',
			name: 'j_password'
		}, {
			xtype: 'checkbox',
			fieldLabel: 'Remember Me?',
			name: '_spring_security_remember_me',
			checked: false
		}],
		buttons: [{
			id: 'lf.btn.login',
			text: 'Login',
			handler: function() {
				fnLoginForm(loginForm);
			}
		},{
			id: 'lf.btn.reset',
			text: 'Reset',
			handler: function() {
				fnResetForm(loginForm);
			}
		}]
	});

});
//Submit login and handler response
function fnLoginForm(theForm)
{
theForm.getForm().submit({
	success: function(form, action) {
		Ext.Msg.alert('Success', 'Login Successful!', function(btn, text) {
			if (btn == 'ok') {
				window.location = homeUrl;
			}
		});
	},
	failure: function(form, action) {
		Ext.Msg.alert('Warning', action.result.errorMessage);
	}
});
} //end fnLoginForm

function fnResetForm(theForm)
{
theForm.getForm().reset();
} //end fnResetForm
Step 3: Implement login handler to return JSON. Remember the authentication handlers declared in applicationContext-security.xml? now you will need implement it to handle succcess and failure case. For this purpose I used Jackson JSON processor and to include it add the dependency to pom.xml.
<!-- Jackson JSON Processor -->
<dependency>
	<groupId>org.codehaus.jackson</groupId>
	<artifactId>jackson-mapper-asl</artifactId>
	<version>1.7.4</version>
</dependency>
public class LoginSuccessHandler implements AuthenticationSuccessHandler
{
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication auth) throws IOException,
			ServletException {

		ObjectMapper mapper = new ObjectMapper();
		LoginStatus status = new LoginStatus(true, auth.isAuthenticated(), auth.getName(), null);
		OutputStream out = response.getOutputStream();
		mapper.writeValue(out, status);
	}

}

public class LoginFailureHandler implements AuthenticationFailureHandler
{
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException auth)
			throws IOException, ServletException {

		ObjectMapper mapper = new ObjectMapper();
		LoginStatus status = new LoginStatus(false, false, null, "Login failed. Try again.");
		OutputStream out = response.getOutputStream();
		mapper.writeValue(out, status);
	}

}
public class LoginStatus
{
  private final boolean success;
  private final boolean loggedIn;
  private final String username;
  private final String errorMessage;

  public LoginStatus(boolean success, boolean loggedIn, String username, String errorMessage) {
    this.success = success;
    this.loggedIn = loggedIn;
    this.username = username;
    this.errorMessage = errorMessage;
  }
}
Time to test drive the code. Lauch http://localhost:8080/spring-mvc-login/ in the browser and you will be redirected to the login page http://localhost:8080/spring-mvc-login/app/login.do.
Some of the things to improve on :
  • Use of SSL
  • Authenticate to LDAP
  • Securing “remember me” using Token.
Hope you find the tutorial useful. Happy coding! 🙂
Update: the source project is downloadable at myGit.
Advertisements