ZK + Spring Security Login form– Additional Login Parameters - Part 4

Extra Spring Login Parameters.

In the last article, we have seen the complete implementation of Spring security Login without Database connection.  In this post, we will see how we can pass additional parameters from the login screen to validate. For example, in the bigger application, apart from user name and password, you may also need to get domain or company name or Client ID, etc..

So our Login screen will look like this

image

Thanks to the following post from which I frame this example
Two-Factor Authentication and Spring Security 3

Technologies used in this example:

1. ZK 6.5.1 CE Version.
2. Spring-security-core 3.0.5

Step 1:
Download this document and setup the development environment.

Step 2:
Let us create ZK Maven Project. Follow the below instruction.
In the Eclipse IDE, Select File –> New-> Other-> Maven Project as shown here.
image_thumb_thumb1_thumb5

Click Next.
image_thumb1_thumb1_thumb1
Uncheck the option “Create a simple Project(skip archetype selection) and Click next.
image_thumb2_thumb1_thumb2
Select “ZK” in the catalog and select “ZK-archetype-webapp 6.0” as shown here."
image_thumb31_thumb1_thumb1
Click Next after selecting the ZK Archetype.
image
Enter “zkloginexample4” for Group ID, Artifact id and Package. And important, please select “ZK-Version-since” from the bottom list and then select finish.
Now, the following folder structure will be created.
image

Step 3:
Next we will include the spring dependency in the POM File.
Double click the POM.XML file in the project navigator.
image_thumb7_thumb_thumb

Click on the POM.XML tab. The following modification has been done.
1. Change the ZK Version to latest CE Version.
2. Remove the Maven ZK EE Version Repository.
3. Add the spring security dependency.
Here is the modified POM.XML file.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>zkloginexample4</groupId>
<artifactId>zkloginexample4</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<zk.version>6.5.1</zk.version>
<commons-io>1.3.1</commons-io>
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
<packname>-${project.version}-FL-${maven.build.timestamp}</packname>
</properties>
<packaging>war</packaging>
<name>The zkloginexample4 Project</name>
<description>The zkloginexample4 Project</description>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE, Version 3</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
<distribution>repo</distribution>
</license>
</licenses>
<repositories>
<repository>
<id>ZK CE</id>
<name>ZK CE Repository</name>
<url>http://mavensync.zkoss.org/maven2</url>
</repository>
<repository>
<id>ZK EVAL</id>
<name>ZK Evaluation Repository</name>
<url>http://mavensync.zkoss.org/eval</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>zkmaven</id>
<name>ZK Maven Plugin Repository</name>
<url>http://mavensync.zkoss.org/maven2/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.zkoss.zk</groupId>
<artifactId>zkbind</artifactId>
<version>${zk.version}</version>
</dependency>
<dependency>
<groupId>org.zkoss.zk</groupId>
<artifactId>zul</artifactId>
<version>${zk.version}</version>
</dependency>
<dependency>
<groupId>org.zkoss.zk</groupId>
<artifactId>zkplus</artifactId>
<version>${zk.version}</version>
</dependency>
<dependency>
<groupId>org.zkoss.zk</groupId>
<artifactId>zhtml</artifactId>
<version>${zk.version}</version>
</dependency>

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io}</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>

<!-- ZK 5 breeze theme <dependency> <groupId>org.zkoss.theme</groupId>
<artifactId>breeze</artifactId> <version>${zk.version}</version> <optional>true</optional>
</dependency> -->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Run with Jetty -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.10</version>
<configuration>
<scanIntervalSeconds>5</scanIntervalSeconds>
<stopKey>foo</stopKey>
<stopPort>9999</stopPort>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Compile java -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<!-- Build war -->
<plugin>
<artifactId>maven-war-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>2.1.1</version>
</plugin>
<!-- Pack zips -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>webapp</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>zkloginexample4${packname}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>assembly/webapp.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>


Step 4:
Next step we will update web.xml to take care spring filter.
Here is the updated web.xml file.


<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<description><![CDATA[My ZK Application]]></description>
<display-name>zkloginexample4</display-name>

<!-- ====================================================== -->
<!-- CONFIGURATION FILES -->
<!-- ====================================================== -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-security.xml
</param-value>
</context-param>

<!-- ====================================================== -->
<!-- SPRING CONTEXT LOADER -->
<!-- ====================================================== -->

<!-- The org.springframework.web.context.ContextLoaderListener class uses
a context parameter called contextConfigLocation to determine the location
of the Spring configuration file. The context parameter is configured using
the context-parameter element. The context-param element has two children
that specify parameters and their values. The param-name element specifies
the parameter's name. The param-value element specifies the parameter's value -->

<listener>
<display-name>Spring Context Loader</display-name>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Spring Security -->
<!-- A servlet filter capturing every user requests and sending them to
the configured security filters to make sure access is authorized. -->
<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>


<!-- //// -->
<!-- ZK -->
<listener>
<description>ZK listener for session cleanup</description>
<listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
</listener>
<servlet>
<description>ZK loader for ZUML pages</description>
<servlet-name>zkLoader</servlet-name>
<servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>

<!-- Must. Specifies URI of the update engine (DHtmlUpdateServlet). It
must be the same as <url-pattern> for the update engine. -->
<init-param>
<param-name>update-uri</param-name>
<param-value>/zkau</param-value>
</init-param>
<!-- Optional. Specifies whether to compress the output of the ZK loader.
It speeds up the transmission over slow Internet. However, if you configure
a filter to post-processing the output, you might have to disable it. Default:
true <init-param> <param-name>compress</param-name> <param-value>true</param-value>
</init-param> -->
<!-- [Optional] Specifies the default log level: OFF, ERROR, WARNING, INFO,
DEBUG and FINER. If not specified, the system default is used. <init-param>
<param-name>log-level</param-name> <param-value>OFF</param-value> </init-param> -->
<load-on-startup>1</load-on-startup><!-- Must -->
</servlet>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zul</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zhtml</url-pattern>
</servlet-mapping>
<!-- [Optional] Uncomment it if you want to use richlets. <servlet-mapping>
<servlet-name>zkLoader</servlet-name> <url-pattern>/zk/*</url-pattern> </servlet-mapping> -->
<servlet>
<description>The asynchronous update engine for ZK</description>
<servlet-name>auEngine</servlet-name>
<servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>

<!-- [Optional] Specifies whether to compress the output of the ZK loader.
It speeds up the transmission over slow Internet. However, if your server
will do the compression, you might have to disable it. Default: true <init-param>
<param-name>compress</param-name> <param-value>true</param-value> </init-param> -->
<!-- [Optional] Specifies the AU extension for particular prefix. <init-param>
<param-name>extension0</param-name> <param-value>/upload=com.my.MyUploader</param-value>
</init-param> -->
</servlet>
<servlet-mapping>
<servlet-name>auEngine</servlet-name>
<url-pattern>/zkau/*</url-pattern>
</servlet-mapping>

<!-- [Optional] Uncomment if you want to use the ZK filter to post process
the HTML output generated by other technology, such as JSP and velocity.
<filter> <filter-name>zkFilter</filter-name> <filter-class>org.zkoss.zk.ui.http.DHtmlLayoutFilter</filter-class>
<init-param> <param-name>extension</param-name> <param-value>html</param-value>
</init-param> <init-param> <param-name>compress</param-name> <param-value>true</param-value>
</init-param> </filter> <filter-mapping> <filter-name>zkFilter</filter-name>
<url-pattern>your URI pattern</url-pattern> </filter-mapping> -->
<!-- //// -->

<!-- ///////////// -->
<!-- DSP (optional) Uncomment it if you want to use DSP However, it is turned
on since zksandbox uses DSP to generate CSS. <servlet> <servlet-name>dspLoader</servlet-name>
<servlet-class>org.zkoss.web.servlet.dsp.InterpreterServlet</servlet-class>
<init-param> <param-name>class-resource</param-name> <param-value>true</param-value>
</init-param> </servlet> <servlet-mapping> <servlet-name>dspLoader</servlet-name>
<url-pattern>*.dsp</url-pattern> </servlet-mapping> -->

<!-- /////////// -->
<!-- [Optional] Session timeout -->
<session-config>
<session-timeout>60</session-timeout>
</session-config>

<!-- [Optional] MIME mapping -->
<mime-mapping>
<extension>doc</extension>
<mime-type>application/vnd.ms-word</mime-type>
</mime-mapping>
<mime-mapping>
<extension>gif</extension>
<mime-type>image/gif</mime-type>
</mime-mapping>
<mime-mapping>
<extension>htm</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>jpeg</extension>
<mime-type>image/jpeg</mime-type>
</mime-mapping>
<mime-mapping>
<extension>jpg</extension>
<mime-type>image/jpeg</mime-type>
</mime-mapping>
<mime-mapping>
<extension>js</extension>
<mime-type>text/javascript</mime-type>
</mime-mapping>
<mime-mapping>
<extension>pdf</extension>
<mime-type>application/pdf</mime-type>
</mime-mapping>
<mime-mapping>
<extension>png</extension>
<mime-type>image/png</mime-type>
</mime-mapping>
<mime-mapping>
<extension>txt</extension>
<mime-type>text/plain</mime-type>
</mime-mapping>
<mime-mapping>
<extension>xls</extension>
<mime-type>application/vnd.ms-excel</mime-type>
</mime-mapping>
<mime-mapping>
<extension>xml</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
<mime-mapping>
<extension>zhtml</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>zul</extension>
<mime-type>text/html</mime-type>
</mime-mapping>

<welcome-file-list>
<welcome-file>index.zul</welcome-file>
<welcome-file>index.zhtml</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
</web-app>

Step 5:
As you can see in the web.xml file, we are loading new xml file configuration named as “spring-security.xml”.
Select the Folder “WEB-INF”, right click, Select New->Other-XML File and give the file name as “spring-security.xml”

image
Paste the following content

<!-- Spring namespace-based configuration -->

<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

<!-- ====================================================== -->
<!-- For catching the @Secured annotation methods -->
<!-- Tells the Spring Security engine that we will use Spring Security's -->
<!-- pre and post invocation Java annotations (@PreFilter, @PreAuthorize, -->
<!-- @PostFilter, -->
<!-- @PostAuthorize) to secure service layer methods.. -->
<!-- Look in GFCBaseCtrl.java onEvent() method. -->
<!-- ====================================================== -->

<!-- Enable the @Secured annotation to secure service layer methods -->
<global-method-security secured-annotations="enabled" />


<beans:bean id="languageFilter"
class="zkloginexample4.ExUsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationFailureHandler"
ref="failureHandler" />
</beans:bean>


<http entry-point-ref="loginUrlAuthenticationEntryPoint">
<intercept-url pattern="/login.zul" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/*.zul" access="IS_AUTHENTICATED_REMEMBERED" />
<intercept-url pattern="/zk/*" access="IS_AUTHENTICATED_REMEMBERED" />
<custom-filter position="FORM_LOGIN_FILTER" ref="languageFilter" />
<logout />
</http>


<beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.zul" />
</beans:bean>


<beans:bean id="failureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login.zul?login_error=1" />
</beans:bean>

<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailsService">
<password-encoder hash="md5" />
</authentication-provider>
</authentication-manager>

<beans:bean id="myUserDetailsService" class="zkloginexample4.MyUserDetailsService" />


</beans:beans>



You can see here, we have setup our own Entry point
The first thing we need to do is extend the UsernamePasswordAuthenticationFilter class so that it can handle a second input field.
So we will create a class under the default package zkloginexample4. Here is the class definition

package zkloginexample4;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

public class ExUsernamePasswordAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {

private String extraParameter = "Company";
private String delimiter = ":";

@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
String dbValue = request.getParameter("Company");

System.out.println("Value is " + dbValue);
request.getSession().setAttribute("dbValue", dbValue);
return super.attemptAuthentication(request, response);
}

@Override
protected String obtainUsername(HttpServletRequest request) {
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getExtraParameter());

String combinedUsername = username + getDelimiter() + extraInput;

System.out.println("Combined username = " + combinedUsername);
return combinedUsername;
}

/**
* @return The parameter name which will be used to obtain the extra input
* from the login request
*/
public String getExtraParameter() {
return this.extraParameter;
}

/**
* @param extraParameter
* The parameter name which will be used to obtain the extra
* input from the login request
*/
public void setExtraParameter(String extraParameter) {
this.extraParameter = extraParameter;
}

/**
* @return The delimiter string used to separate the username and extra
* input values in the string returned by
* <code>obtainUsername()</code>
*/
public String getDelimiter() {
return this.delimiter;
}

/**
* @param delimiter
* The delimiter string used to separate the username and extra
* input values in the string returned by
* <code>obtainUsername()</code>
*/
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
}


As you can see, we have created additional two java classes such as DbUser.java and UserDAO.java. So let us create this two classes in the package zkloginexample4 as shown.

image
Here is the code for DBUser.Java


package zkloginexample4;

public class DbUser {

/**
* The username
*/
private String username;

/**
* The password as an MD5 value
*/
private String password;

/**
* Access level of the user. 1 = Admin user 2 = Regular user
*/
private Integer access;

/**
* The company Name
*/
private String companyName;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Integer getAccess() {
return access;
}

public void setAccess(Integer access) {
this.access = access;
}

public String getCompanyName() {
return companyName;
}

public void setCompanyName(String companyName) {
this.companyName = companyName;
}

}



Here is the code for UserDAO.java



package zkloginexample4;

import java.util.ArrayList;
import java.util.List;

public class UserDAO {

/**
* Simulates retrieval of data from a database.
* http://krams915.blogspot.in/2010/12/spring-security-mvc-integration_18.html
*/
public DbUser searchDatabase(String username, String companyName) {
// Retrieve all users from the database
List<DbUser> users = internalDatabase();

// Search user based on the parameters
for (DbUser dbUser : users) {
if (dbUser.getUsername().equals(username) == true && dbUser.getCompanyName().equals(companyName) == true) {
// return matching user
return dbUser;
}
}
System.out.println("Not Exists");
throw new RuntimeException("User does not exist!");
}

/**
* Our fake database. Here we populate an ArrayList with a dummy list of
* users.
*/
private List<DbUser> internalDatabase() {
// Dummy database

// Create a dummy array list
List<DbUser> users = new ArrayList<DbUser>();
DbUser user = null;

// Create a new dummy user
user = new DbUser();
user.setUsername("rod");
// Actual password: admin
user.setPassword("a564de63c2d0da68cf47586ee05984d7");
// Admin user
user.setAccess(1);
user.setCompanyName("abc");

// Add to array list
users.add(user);

// Create a new dummy user
user = new DbUser();
user.setUsername("dianne");
// Actual password: user
user.setPassword("65d15fe9156f9c4bbffd98085992a44e");
// Regular user
user.setAccess(2);
user.setCompanyName("abc");

// Add to array list
users.add(user);

// Create a new dummy user
user = new DbUser();
user.setUsername("scott");
// Actual password: user
user.setPassword("2b58af6dddbd072ed27ffc86725d7d3a");
// Regular user
user.setAccess(2);
user.setCompanyName("abc");

// Add to array list
users.add(user);

// Create a new dummy user
user = new DbUser();
user.setUsername("scott");
// Actual password: user
user.setPassword("22b5c9accc6e1ba628cedc63a72d57f8");
// Regular user
user.setAccess(2);
user.setCompanyName("xyz");

// Add to array list
users.add(user);

return users;
}
}





Step 6:
Next step is to add the login.zul. Select the folder “webapp”. Right click, Select New->Other->Zul file. Enter the file name as login.zul
Here is the file content.


<?page id="loginPage" title="Digital medical sheet - Login page" onLoad="win.doOverlapped();"?>
<window id="win" title="LOGIN" border="normal" width="300px"
position="center">

<!-- this form-login-page form is also used as the
form-error-page to ask for a login again. -->
<html style="color:red" if="${not empty param.login_error}">


<![CDATA[ Your login attempt was not successful, try
again.<br/><br/> Reason:
${SPRING_SECURITY_LAST_EXCEPTION.message} ]]>


</html>

<groupbox>
<caption>Login</caption>
<h:form id="f" name="f" action="j_spring_security_check"
method="POST" xmlns:h="http://www.w3.org/1999/xhtml">
<grid>
<columns sizable="false">
<column width="50%" />
<column width="50%" />
</columns>
<rows>
<row>
<label value="User:" />
<textbox id="u" name="j_username" />
</row>
<row>
<label value="Password:" />
<textbox id="p" type="password"
name="j_password" />
</row>

<row>
<label value="Company Name" />
<textbox id="pa" name="Company" />
</row>



<row spans="2">
<div align="center">
<h:input type="submit" value="login" />
<h:input type="reset" value="Reset" />
</div>
</row>
</rows>
</grid>
</h:form>
</groupbox>
</window>

Step 7:
Now the let us change the index.zul file content which is created by default.

<zk>
<window >
Welcome to ZK Secured Page
<button label="Logout" href="/j_spring_security_logout"/>
</window>
</zk>


Step 8:
Now you can select the Project “zkloginexample4”, right click-> Run as –> Run on Server
image_thumb4_thumb_thumb
Select the tomcat server and Click finish button

image
Now you can enter any valid username/passwords as follows


rod/koala/abc
dianne/emu/abc
scott/wombat/abc
scott/wombat/xyz



You can download the source here

Reference
1.
Introduction to Spring Security Concepts.
2. Getting Started Spring Security
3. Understanding Spring Security
4. Configuring spring security
5. Spring Security 3 - MVC Integration Tutorial (Part 2)