Customizing Spring Security Login using ExtJS in Grails Apps

I’ve been wanting to learn Grails for sometime and finally had the time to try out this web application framework. Grails is a very impressive framework, it is really about productivity and simplicity. Some of the benefits are scaffolding, testing framework, easy to create plugin and seamless integration with Spring and Hibernate. As part of my first attempt, I am customizing the Spring Security Login using ExtJS in a simple Grails application. The focus here is on the integration with Spring Security and ExtJS rather than Grails. I am using SpringSource Tool Suite (STS 2.7.1) to develop the demo application. The application is to display Quotes randomly and ability to maintain them. Let’s begin the integration and customization steps.

Step 1: Install the Spring Security Plugin

Spring Security is not built-in Grails, to begin the integration execute the following Grails command.

install-plugin spring-security-core

Step 2: Generate Domain Classes

 This step will create basic domain classes needed to store user information and the controller that handle the authentication. After executing the command below you will see several files being added to your project, such as LoginController, auth.gsp, denied.gsp, User, Role, UserRole, RequestMap. The domain class name for user and role is up to you to define and if you have an existing user domain, you must decide to integrate it (possibly by extending your domain class to the generated user class). Just a note that the RequestMap is an option and will look into it at later step.

s2-quickstart org.xaab.qotd User Role RequestMap

Step 3: Define URL mapping for login and logout

In order the make the login and logout controller reachable, the URL mappings must be added into UrlMapping.groovy

class UrlMappings {
	static mappings = {
		...
		"/login/$action?"(controller: "login")
		"/logout/$action?"(controller: "logout")
	}
}

Step 4: Add Access Control to the Controller

The need for this step depends on how you wants to control the access. You can use @Secured annotation to limit access to certain part of the application. In this example, I am not using annotation to limit the access, instead I define dynamics request maps which is shown in the Step 5. The following code is just an example of how @Secured can be used in the controller.

package org.example
import grails.plugins.springsecurity.Secured

class PostController {
    ...
    @Secured(['ROLE_USER'])
    def followAjax = { ... }

    @Secured(['ROLE_USER', 'IS_AUTHENTICATED_FULLY'])
    def addPostAjax = { ... }

    def global = { ... }

    @Secured(['ROLE_USER'])
    def timeline = { ... }

    @Secured(['IS_AUTHENTICATED_REMEMBERED'])
    def personal = { ... }
}

Step 5: Add Dynamic Request Maps

The request maps class was generated in Step 2 and used to limit access by defining the access control at the URL. Here I create the request map in BootStrap.groovy for demo purpose, in your actual application this may be defined in configuration file or database. I don’t think you should choose between using annotation and request map. You can strike balance by implementing both to handle general and fine-grain access control.

import org.xaab.qotd.*

class BootStrap {
    def springSecurityService

    def init = { servletContext ->
		...
		new Requestmap(url: '/**', configAttribute: 'IS_AUTHENTICATED_FULLY').save()
		new Requestmap(url: '/quote/**', configAttribute: 'ROLE_ADMIN,IS_AUTHENTICATED_FULLY').save()

		new Requestmap(url: '/js/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/css/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/images/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/ext-4.0.2/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/quote/random', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/login/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		new Requestmap(url: '/logout/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save()
		...
    }
    def destroy = {
    }
}

Step 6: Amend related GSP to add login and logout link

Amend related GSP (random.gsp) to ensure you have the login and logout link. Since I used scaffolding, I must install the templates and ensure the GSP templates are modified.  Once scaffolding templates are installed, you will see the folders /src/templates/scaffolding/**. The files I modified in this sample application are create.gsp, edit.gsp, list.gsp & show.gsp.

install-templates
...</pre>
<div class="nav"><span class="menuButton">Next Quote</span> <span class="menuButton">Admin</span> <span class="menuButton">Login</span> <span class="menuButton"> (Logout)</span></div>
<pre>...

Step 7: Customize the auth.gsp to Add ExtJs Login Form

By now the integration is completed and if you used the test data in Step 10, you will be able to see the default login page. In order to customize it, copy the ExtJS source into /web-app and in this sample application I am using ExtJS 4.0.2. Start the customization by modifying the auth.gsp as shown below. Do take note on the naming of the fields as it is the convention used in Spring Security.

Login<script type="text/javascript" src="${resource(dir:'ext-4.0.2',file:'ext-all.js')}"></script><script type="text/javascript">// <![CDATA[
		var defLoginUrl = '${postUrl}';
		var homeUrl = '${createLink(uri: "/quote/random")}';

// ]]></script>
<script type="text/javascript">// <![CDATA[
Ext.onReady(function(){
	Ext.QuickTips.init();

	var loginForm = Ext.create('Ext.form.Panel',{
			url: defLoginUrl,
			title: 'Login',
			renderTo: 'login',
			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);
					}
			}]
	});

});

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; //optionally this can be part of the data return by the server.
				}
			});
		},
		failure: function(form, action) {
			Ext.Msg.alert('Warning', action.result.error);
		}
	});
} //end fnLoginForm

function fnResetForm(theForm)
{
	theForm.getForm().reset();
} //end fnResetForm
// ]]></script>

Step 8: Configure Authentication Success and Failure Handler

The ExtJS login form will perform HTTP post to submit the login data however it expects JSON data as response. The default method in the LoginController will not be able to handle this, thus you must define your own handler. Add your handler into Config.groovy as shown below. The handlers are implemented in LoginController in the next step.

grails.plugins.springsecurity.successHandler.defaultTargetUrl = '/login/authSucccessExtJs'
grails.plugins.springsecurity.successHandler.alwaysUseDefault = true
grails.plugins.springsecurity.failureHandler.defaultFailureUrl = '/login/authFailExtJs?login_error=1'

Step 9: Implement the Handler in LoginController

After adding the handler, I suggest you remove or comment out the other methods to ensure they are not accessible.

import grails.converters.JSON

import javax.servlet.http.HttpServletResponse

import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils

import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.authentication.LockedException
import org.springframework.security.core.context.SecurityContextHolder as SCH
import org.springframework.security.web.WebAttributes
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

class LoginController {

	/**
	 * Dependency injection for the authenticationTrustResolver.
	 */
	def authenticationTrustResolver

	/**
	 * Dependency injection for the springSecurityService.
	 */
	def springSecurityService
	...
		/**
	 * The ExtJS Authentication success handler
	 */
	def authSucccessExtJs = {
		render([success: true, username: springSecurityService.authentication.name] as JSON)
	}

	/**
	 * The ExtJS Authentication failure handler
	 */
	def authFailExtJs = {
			def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
			String msg = ''
			def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
			if (exception) {
				if (exception instanceof AccountExpiredException) {
					msg = SpringSecurityUtils.securityConfig.errors.login.expired
				}
				else if (exception instanceof CredentialsExpiredException) {
					msg = SpringSecurityUtils.securityConfig.errors.login.passwordExpired
				}
				else if (exception instanceof DisabledException) {
					msg = SpringSecurityUtils.securityConfig.errors.login.disabled
				}
				else if (exception instanceof LockedException) {
					msg = SpringSecurityUtils.securityConfig.errors.login.locked
				}
				else {
					msg = SpringSecurityUtils.securityConfig.errors.login.fail
				}
			}

			render([success: false, error: msg] as JSON)
	}
}

Step 10: Setup Test Data

So, you have integrated your Grails application, customized the login form using ExtJS. Now, setup the test data in BootStrap.groovy and launch the application (http://localhost:8080/grails-qotd-extjs).

import org.xaab.qotd.*

class BootStrap {
    def springSecurityService

    def init = { servletContext ->
		new Quote(author: "Unknown", content: "If you think sunshine brings you happiness, then you haven’t danced in the rain.").save()
		new Quote(author: "Lao Tzu", content: "An ant on the move does more than a dozing ox.").save()
		new Quote(author: "Tony Robbins", content: "It not knowing what to do, it’s doing what you know.").save()
		new Quote(author: "Bruce Lee", content: "Simplicity is the key to brilliance.").save()

		...

		def userRole = Role.findByAuthority('ROLE_USER') ?: new Role(authority: 'ROLE_USER').save(failOnError: true)
		def adminRole = Role.findByAuthority('ROLE_ADMIN') ?: new Role(authority: 'ROLE_ADMIN').save(failOnError: true)

		def adminUser = User.findByUsername('admin') ?: new User(
			username: 'admin',
			password: springSecurityService.encodePassword('admin'),
			enabled: true).save(failOnError: true)

		if (!adminUser.authorities.contains(adminRole)) {
			UserRole.create(adminUser, adminRole)
		}
    }
    def destroy = {
    }
}

Hope this tutorial helps and happy coding!

download source

References:

Display JSON Data using ExtJS 4 Store

I am sharing a short example of how to display JSON data using ExtJS 4 Store. In previous version of ExtJS, there is a small helper class call JsonStore which is used to simplify creation Stores from JSON data. In ExtJS 4 the class is no longer there. In this example, I am retrieving USGS data as JSON and display it in Grid as shown below.

USGS Data in Gri

As shown below in line 20, I use proxy of type JSONP in Ext.data.Store to retrieve the JSON data.

Ext.onReady(function(){
  Ext.define('UsgsList', {
    extend: 'Ext.data.Model',
    fields: [
       {name: 'fid',       type: 'int'},
       {name: 'title',     type: 'string'},
       {name: 'description',  type: 'string'},
       {name: 'link',      type: 'string'},
       {name: 'pubDate',    type: 'date'},
       {name: 'lat',      type: 'string'},
       {name: 'long',      type: 'string'}
    ],
    idProperty: 'fid'
});

var store = Ext.create('Ext.data.Store', {
    id: 'store',
    model: 'UsgsList',
    proxy: {
       type: 'jsonp',
       url: 'http://query.yahooapis.com/v1/public/yql',
    extraParams: {
       q: 'select * from rss where url="http://earthquake.usgs.gov/earthquakes/catalogs/eqs7day-M2.5.xml"',
       format: 'json'
   },
   reader: {
      root: 'query.results.item',
   }
 }
});

function renderTitle(value, p, record) {
   return Ext.String.format('<a href="{1}" target="_blank">{0}</a>',
   value,
   record.data.link
   );
}

var grid = Ext.create('Ext.grid.Panel', {
   width: 700,
   height: 500,
   title: 'USGS - M2.5+',
   store: store,
   loadMask: true,
   disableSelection: true,
   invalidateScrollerOnRefresh: false,
   viewConfig: {
     trackOver: false
   },
   // grid columns
   columns:[{
      xtype: 'rownumberer',
      width: 50,
      sortable: false
   },{
      id: 'title',
      text: "Title",
      dataIndex: 'title',
      flex: 1,
      renderer: renderTitle,
      sortable: false
   },{
      id: 'pubDate',
      text: "Published Date",
      dataIndex: 'pubDate',
      width: 130,
      renderer: Ext.util.Format.dateRenderer('n/j/Y g:i A'),
      sortable: true
   }],
   renderTo: Ext.getBody()
});

// trigger the data store load
store.load();
});

Integrating JSR303 with Spring MVC 3 and ExtJS Forms

Previously in Spring MVC and ExtJS Forms I created a simple data entry form and there was no data validation implemented. Typically data validation occurs throughout application layers, from presentation to persistence layer. Here I will be using JSR-303 Bean Validation, where the data validation is defined in the domain model and can be used thoughout application layers. I will walk through how to integrate data validation into Spring MVC and will be using Hibernate Validator 4.x – which is the reference implementation for JSR-303 – to integrate into my previous example.

What is JSR-303?

“This JSR defines a metadata model and API for JavaBean validation. The default metadata source is annotations,
with the ability to override and extend the meta-data through the use of XML validation descriptors.
The validation API developed by this JSR is not intended for use in any one tier or programming model. It is specifically
not tied to either the web tier or the persistence tier, and is available for both server-side application programming,
as well as rich client Swing application developers.” – JSR-303 Specification

The data validation is achieved by defining constraints in the beans. Constraints are defined by a combination of constraint annotation and a list of constraint validation implementations. The constraint annotation can be applied at field-level, property-level (getter), class-level. Currently there are 22 built-in constraints for example @NotNull to check that the annotated value is not null or @Size(min=, max=) to check whether the annotated value lies between the specified range. You can also create your own custom constraint to meet your specific requirement. The concept here is similar to database constraints where the constraint can be applied to table column.

For now, I will walk through how to use the built-in constraints only. As usual, I will start with the configuration required to make the project work. Open the Maven pom.xml and add the following dependencies and repository:

<!-- JSR 303 with Hibernate Validator -->
<dependency>
	<groupId>javax.validation</groupId>
	<artifactId>validation-api</artifactId>
	<version>1.0.0.GA</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>4.1.0.Final</version>
</dependency>
...
<!-- For Hibernate Validator Repository -->
<repository>
	<id>org.jboss.repository.release</id>
	<name>JBoss Maven Release Repository</name>
	<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
	<snapshots><enabled>false</enabled></snapshots>
</repository>

In the example, the form is submitted to HomeController and the form is mapped to PersonalContact bean. I added 3 built-in constraints into the PersonalContact bean, @NotBlank, @Patter (using regular expression) and @Email. When message is not define in the constraint annotation, the validator will assign a default value. The second part of the code below shows the validation process. The HomeController is autowired to the Validator bean and the validation() is executed inside the add().

public class PersonalContact implements Serializable
{
	private static final long serialVersionUID = 1L;
	private Long id;

	@NotBlank(message="Please enter name")
	private String name;
               // using regular expression to define the constraint
	@Pattern(regexp="[0-9]{8}", message="Please enter 8 digit phone number")
	private String phone;

	@Email
	private String email;

	public PersonalContact() {}

	public PersonalContact(Long id, String name, String phone, String email) {
		super();
		this.id = id;
		this.name = name;
		this.phone = phone;
		this.email = email;
	}
	//getter & setter
}
@Controller
public class HomeController
{
	@Autowired
	private Validator validator;

	@RequestMapping(value="/", method=RequestMethod.GET)
	public String home() {
		return "home";
	}

	@RequestMapping(value="/load", method=RequestMethod.POST)
	public @ResponseBody Map<String, ? extends Object> load(PersonalContact input) {
	...
	}

	@RequestMapping(value="/add", method=RequestMethod.POST)
	public @ResponseBody Map<String, ? extends Object> add(PersonalContact input, HttpSession session) {
		Map<String, Object> data = new HashMap<String, Object>();

		Set<ConstraintViolation<PersonalContact>> failures = validator.validate(input);
		if (!failures.isEmpty()) {
			//structure the response for ExtJS Form.
			data.put("success",Boolean.FALSE);
			data.put("errors", validationMessages(failures));
			data.put("errorMessage", "Add Failed!");
		} else {
			session.setAttribute(input.getName(), input);
			data.put("success",Boolean.TRUE);
		}

		return data;
	}
	//iterate to retrieve validation errors and store it in HashMap
	private Map<String, String> validationMessages(Set<ConstraintViolation<PersonalContact>> failures) {
		Map<String, String> failureMessages = new HashMap<String, String>();
		for (ConstraintViolation<PersonalContact> failure : failures) {
			failureMessages.put(failure.getPropertyPath().toString(), failure.getMessage());
		}
		return failureMessages;
	}
}

Since I used ExtJS forms, I must return JSON response in a specific format and the errors are mapped to the field and displayed as quicktips.


The spring-mvc-forms project is updated and available for download at myGit. Hope you find it useful. Happy coding! 🙂

References

{
   success: true,
   errors: { field1: 'error for field 1',
                 field2: 'error for field 2',
   	   ...
   }
}

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.

Spring 3 MVC and ExtJS Forms

In my previous post Beginning Spring MVC 3, I shared a simple tutorial to start developing Spring MVC 3. It covered the tools required and the steps to setup the project. This tutorial will take a step further and walk through how to work with forms – Ext JS form to be specific – for loading of data and submission of data for processing. I will be assuming that you have the tools and project setup.

Step 1. The model. Here I assumed a single bean will represent the form and data model (for data access/persistence). The attributes in the bean are mapped to the form fields.

public class PersonalContact implements Serializable
{
	private static final long serialVersionUID = 1L;
	private Long id;
	private String name;
	private String phone;
	private String email;

	public PersonalContact() {}

	public PersonalContact(Long id, String name, String phone, String email) {
		super();
		this.id = id;
		this.name = name;
		this.phone = phone;
		this.email = email;
	}

	//regenerate the getter & setter using STS.
}

Step 2. The controller. Using the default “HomeController”, I added 2 methods to handle the load and add request. Both methods have simple implementation to illustrate the interaction between the form and Spring MVC. There is no data access/persistence code implemented here.

@Controller
public class HomeController {

	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@RequestMapping(value="/", method=RequestMethod.GET)
	public String home() {
		logger.info("Welcome home!");
		return "home";
	}

	@RequestMapping(value="/load", method=RequestMethod.POST)
	public @ResponseBody Map<String, ? extends Object> load(PersonalContact input) {
		logger.info("Inside load");
		PersonalContact pc = new PersonalContact(1L, "Wowi", "89281932", "wo.wi@abcxyz.com");
		Map<String, Object> data = new HashMap<String, Object>();
		data.put("success",Boolean.TRUE);
		data.put("data", pc);

		return data;
	}

	@RequestMapping(value="/add", method=RequestMethod.POST)
	public @ResponseBody Map<String, ? extends Object> add(PersonalContact input, HttpSession session) {
		logger.info("Inside add");

		Map<String, Object> data = new HashMap<String, Object>();

		if (input.getName() == null) {
			data.put("success",Boolean.FALSE);
			data.put("errorMessage", "No Name?");
		} else if (session.getAttribute(input.getName()) != null) {
			data.put("success",Boolean.FALSE);
			data.put("errorMessage", "There is an existing data, unable to add. Please enter a different name");
		} else {
			session.setAttribute(input.getName(), input);
			data.put("success",Boolean.TRUE);
		}

		return data;
	}
}

Step 3. The view. Similar to the controller, I built on the default home.jsp. As you can see there isn’t much code in the JSP. The core UI codes are in the JavaScript (spring-mvc-forms.js). By default the response assumed to be in JSON. Do remember to add the Jackson into the pom.xml and set the header to “Accept” application/json.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
	<link href="<c:url value="/resources/lib/ext-3.3.1/resources/css/ext-all.css" />" rel="stylesheet" type="text/css" />
	<script type="text/javascript" src="<c:url value="/resources/lib/ext-3.3.1/adapter/ext/ext-base.js" />"></script>
	<script type="text/javascript" src="<c:url value="/resources/lib/ext-3.3.1/ext-all.js" />"></script>
	<script type="text/javascript">
		if (Ext.BLANK_IMAGE_URL.substr(0, 5) != 'data:') {
			Ext.BLANK_IMAGE_URL = '<c:url value="/resources/lib/ext-3.3.1/resources/images/default/s.gif" />';
		}
		var loadUrl = '<c:url value="/load" />';
		var addUrl = '<c:url value="/add" />';
	</script>
	<script type="text/javascript" src="<c:url value="/resources/js/spring-mvc-forms.js" />"></script>
	<style type="text/css">
		body {
		  	font: normal 12px helvetica,arial,verdana,tahoma,sans-serif;
		}
		.my-form-class {
			margin:  20px 30px;
		}
	</style>
</head>
<body>
</body>
</html>
Ext.onReady(function(){
	Ext.QuickTips.init();

	var mf = new Ext.FormPanel({
			url: addUrl,
			renderTo: Ext.getBody(),
			frame: true,
			cls: 'my-form-class',
			width: 350,
			items: [{
					xtype: 'textfield',
					fieldLabel: 'Name',
					name: 'name'
			},{
					xtype: 'textfield',
					fieldLabel: 'Phone No.',
					name: 'phone'
			},{
					xtype: 'textfield',
					fieldLabel: 'EMail',
					name: 'email'
			}],
			buttons: [{
					id: 'mf.btn.load',
					text: 'Load',
					handler: function() {
						fnLoadForm(mf);
					}
				},{
					id: 'mf.btn.add',
					text: 'Add',
					disabled: true,
					handler: function() {
						fnUpdateForm(mf);
					}
			},{
					id: 'mf.btn.reset',
					text: 'Reset',
					disabled: true,
					handler: function() {
						fnResetForm(mf);
					}
			}]
	});

});

function fnLoadForm(theForm)
{
	//for the purpose of this tutorial, load 1 record.
	theForm.getForm().load({
		url: loadUrl,
		headers: {Accept: 'application/json, text/javascript, */*; q=0.01'},
    waitMsg: 'loading...',
		params : {
			id: 1
		},
		success: function(form, action) {
			Ext.getCmp('mf.btn.add').setDisabled(false);
			Ext.getCmp('mf.btn.reset').setDisabled(false);
			Ext.getCmp('mf.btn.load').setDisabled(true);
		},
		failure: function(form, action) {
			Ext.Msg.alert('Warning', 'Error Unable to Load Form Data.');
		}
	});
} //end fnLoadForm
function fnUpdateForm(theForm)
{
	theForm.getForm().submit({
		success: function(form, action) {
			Ext.Msg.alert('Success', 'Data is stored in session.');
			form.reset();
		},
		failure: function(form, action) {
			Ext.Msg.alert('Warning', action.result.errorMessage);
		}
	});
} //end fnUpdateForm
function fnResetForm(theForm)
{
	theForm.getForm().reset();
	Ext.getCmp('mf.btn.add').setDisabled(true);
	Ext.getCmp('mf.btn.reset').setDisabled(true);
} //end fnResetForm

Now that all the components are ready, launch the example and to begin with click “load” button. The form will be pre-populated with example data. To try the form submission, click “add”. 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);