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.

ExtJS Google Maps Panel

I was browsing the Ext JS (3.3.x) example and saw the Google Maps Panel which was written based on Google Maps API v2. I thought it might be a good idea to migrate it to use v3, since v2 has been officially deprecated for sometime. Beside migrating the code, I modified the input parameter to accept MapOptions and also added 2 new features:

  • option to show InfoWindow at the center & other markers.
  • option to draw circle overlay at the center marker.

The full source code and example is available at myGit. I did not include Ext JS 3.3.1 into the repository.

Ext.ux.GoogleMapPanel = Ext.extend(Ext.Panel, {
		...
    addMarker : function(point, marker, clear, center, listeners, infoWindowOptions){
        if (clear === true){
            this.getMap().clearOverlays();
        }
        if (center === true) {
            this.getMap().setCenter(point);
        }

				var mark = new google.maps.Marker({
				       map: this.getMap(),
				       position: point,
				       title: marker.title
				});

				var infoWindow = null;
				if (infoWindowOptions != null) {
				   infoWindow = new google.maps.InfoWindow(infoWindowOptions);
				   	google.maps.event.addListener(mark, 'click', function() {
			  			infoWindow.open(this.getMap(),mark);
					});
				}
        if (typeof listeners === 'object'){
            for (evt in listeners) {
                google.maps.event.addListener(mark, evt, listeners[evt]);
            }
        }
        return mark;
    },
    drawCircle: function(circleOptions) {
	      this.circleOverlays = new google.maps.Circle(circleOptions);
        this.circleOverlays.setCenter(this.getCenter());
        this.circleOverlays.setMap(this.getMap());
     }
     ...
});

Ext.reg('gmappanel', Ext.ux.GoogleMapPanel);     

ExtJS and ASP.NET MVC 3: CRUD DataGrid

This short tutorial will walk though the implementation of DataGrid using ExtJS 3.3.1 and ASP.NET MVC 3. In this tutorial I focus more on the integration of the front-end, so you will not find any database code. Whenever we deal with data we usually create, read/retrieve, update or delete them. ExtJS provides a great data grid component and the ability to perform CRUD operation with very little coding. I used JSON as data format in the example below.

ExtJS DataGrid
ExtJS DataGrid

Let’s start with the server-side by developing the data model and controller.

Controller:

    public class ContactController : Controller
    {
        //
        // GET: /Contact/

        public ActionResult Index()
        {
            return View();
        }

        public JsonResult Load()
        {
            var contact = new List<Contact> {
                new Contact("Smith","95746325","smith@me.com"),
                new Contact("Adam","87291034","adam@me.com"),
                new Contact("Eve","98271345","eve@me.com"),
                new Contact("Chun Li","81728312","chun.li@me.com")
            };
            return Json(new
            {
                total = contact.Count,
                data = contact,
            }, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public JsonResult Create(List<Contact> data)
        {
            //insert Create code
            return Json(new
            {
                data = new Contact(data[0].Name, data[0].Phone, data[0].Email),
                success = true,
                message = "Create method called successfully"
            });
        }

        [HttpPost]
        public JsonResult Update(List<Contact> data)
        {
            //insert Update code
            return Json(new
            {
                success = true,
                message = "Update method called successfully"
            });
        }

        [HttpPost]
        public JsonResult Delete(List<string> data)
        {
            //insert Delete code
            return Json(new
            {
                success = true,
                message = "Delete method called successfully"
            });
        }
    }

Data Model:

    public class Contact
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }

        public Contact(string pName, string pPhone, string pEmail)
        {
            this.Id = System.Guid.NewGuid().ToString();
            this.Name = pName;
            this.Phone = pPhone;
            this.Email = pEmail;
        }

        public Contact() { }
    }

ExtJS:

Now, you move on to the view and work on the ExtJS. First, you define the record type which matches the server-side object.

        var Contact = Ext.data.Record.create([
            {
                name: 'Id',
                type: 'string'
            }, {
                name: 'Name',
                type: 'string'
            }, {
                name: 'Phone',
                type: 'string'
            }, {
                name: 'Email',
                type: 'string'
            }
        ]);

Now you need to setup the Writer and Reader

        var writer = new Ext.data.JsonWriter({
            encode: false,
            listful: true,
            writeAllFields: true
        });

        var reader = new Ext.data.JsonReader({
            totalProperty: 'total',
            successProperty: 'success',
            idProperty: 'Id',
            root: 'data',
            messageProperty: 'message'  // <-- New "messageProperty" meta-data
        }, Contact);

Then, setup a proxy to define the connection to the controller.

        var proxy = new Ext.data.HttpProxy({
            api: {
                read: '/Contact/Load',
                create: '/Contact/Create',
                update: '/Contact/Update',
                destroy: '/Contact/Delete'
            },
            headers: { 'Content-Type': 'application/json; charset=UTF-8' }
        });

Hooks the above components (reader, writer, proxy) to the store.

        var store = new Ext.data.Store({
            id: 'user',
            proxy: proxy,
            reader: reader,
            writer: writer,
            autoSave: false
        });

Add the data grid declaration

        var grid = new Ext.grid.GridPanel({
            store: store,
            columns: [
                { header: "Name",
                    width: 170,
                    sortable: true,
                    dataIndex: 'Name',
                    editor: {
                        xtype: 'textfield',
                        allowBlank: false
                    }
                },
                { header: "Phone No.",
                    width: 160,
                    sortable: true,
                    dataIndex: 'Phone',
                    editor: {
                        xtype: 'textfield',
                        allowBlank: false
                    }
                },
                { header: "EMail",
                    width: 170,
                    sortable: true,
                    dataIndex: 'Email',
                    editor: {
                        xtype: 'textfield',
                        allowBlank: false
                    }
                }
            ],
            plugins: [editor],
            title: 'Contacts DataGrid',
            height: 300,
            width: 510,
            tbar: [{
                iconCls: 'icon-user-add',
                text: 'Add Contact',
                handler: function () {
                    var e = new Contact({
                        Name: 'New Friend',
                        Phone: '(65) 89182736',
                        Email: 'new@google.com'
                    });
                    editor.stopEditing();
                    store.insert(0, e);
                    grid.getView().refresh();
                    grid.getSelectionModel().selectRow(0);
                    editor.startEditing(0);
                }
            }, {
                ref: '../removeBtn',
                iconCls: 'icon-user-delete',
                text: 'Remove Contact',
                handler: function () {
                    editor.stopEditing();
                    var s = grid.getSelectionModel().getSelections();
                    for (var i = 0, r; r = s[i]; i++) {
                        store.remove(r);
                    }
                }
            }, {
                iconCls: 'icon-user-save',
                text: 'Save All Modifications',
                handler: function () {
                    store.save();
                }
            }]
        });

Some observations:

  • When submitting data as JSON, set the headers “Content-Type” to “application/json” in the proxy object and set the encoding to false in the JsonWriter. If not, it will be treated as form submission.
  • Since I set the auto save to false. DataGrid will submit list of contact when there are 2 or more changes, however it will send a single object when there is 1 change. In order to make it consistent to send as list, set list full to true in the JsonWriter.
  • ASP.NET MVC 3 able to recognize the JSON without additional coding.

If you want to see the complete source code, download it from here. Hope it’s useful. 🙂

Happy hacking!