Spring security 3 integration with hibernate 4
What’s up next !. As per the last(Part 5) article, we have successfully completed our CRUD Operation and we completely change the look and feel (I hope it looks good).
We have user details with user id and password. But where is the login screen ?. Ok that’s what we are going to see how to develop login screen for our application. We will integrate Spring security 3 to secure our application using our application.
For introduction about spring security, Please read the following article.
Introduction to Spring Security Concepts
Other Reference
1. Getting Started Spring Security
2. Understanding Spring Security
3. Configuring spring security
If you want to try to ZK with spring security, then read this step by step article.
Let us start now.
Step 1:
First we need to update our POM.XML with spring security core dependency. Spring web and spring orm version does not match with spring security version. We will use later spring web and spring security dependency, but with different version. As of this date, latest version of spring web is 3.2.2 Release and same for Spring ORM. For spring security, we we will use 3.2.0.M1(You can check here and here)
So here is the updated pom.xml file content
<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>ZKBeanValidation</groupId>
<artifactId>ZKBeanValidation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<zk.version>6.5.2</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>
<spring.version>3.2.2.RELEASE</spring.version>
<spring-security.version>3.2.0.M1</spring-security.version>
</properties>
<packaging>war</packaging>
<name>The ZKBeanValidation Project</name>
<description>The ZKBeanValidation 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>repository.springsource.milestone</id>
<name>SpringSource Milestone Repository</name>
<url>http://repo.springsource.org/milestone</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 framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency> <!-- Used for Hibernate4 LocalSessionFactoryBean -->
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!-- AOP dependency -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<!-- Persistence Management -->
<dependency> <!-- Apache BasicDataSource -->
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency> <!-- MySQL database driver -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.10.Final</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>ZKBeanValidation${packname}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>assembly/webapp.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Step 2:
Next we will update our web.xml. Here is the code snippet(See the full content from the download).
.......
<!-- Spring can be easily integrated into any Java-based web framework.
All you need to do is to declare the ContextLoaderListener in your web.xml
and use a contextConfigLocation <context-param> to set which context files
to load. If you don't specify the contextConfigLocation context parameter,
the ContextLoaderListener will look for a /WEB-INF/applicationContext.xml
file to load. Once the context files are loaded, Spring creates a WebApplicationContext
object based on the bean definitions and puts it into the ServletContext. -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<!-- Loads the Spring web application context -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 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>
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- //// -->
<!-- ZK -->
.......
Step 3:
As you can see in the web.xml, we are loading another xml file configuration called as “spring-security.xml”. So let us create this file under our web-inf folder as shown
<!-- 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.1.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" />
<http auto-config="true">
<!-- ====================================================== -->
<!-- If we have our own LoginPage. So we must -->
<!-- tell Spring the name and the place. -->
<!-- In our case we take the same page -->
<!-- for a error message by a failure. -->
<!-- Further the page after a successfully login. -->
<!-- ====================================================== -->
<form-login login-page="/login.zul"
authentication-failure-url="/login.zul?login_error=1"
default-target-url="/main.zul" always-use-default-target="true" />
<intercept-url pattern="/login.zul" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/*.zul" access="IS_AUTHENTICATED_REMEMBERED" />
</http>
<authentication-manager>
<authentication-provider user-service-ref="myUserDetailsService">
</authentication-provider>
</authentication-manager>
<beans:bean id="myUserDetailsService" class="zkexample.service.MyUserDetailsService" />
</beans:beans>
Step 4:
As you can see in the security xml file, we have said we will have our login (login.zul) form instead of spring default login page. So let us create our login page. Everybody want to create stylish login page for their application. There are lot of free login page available with CSS. So I will be using one of them. You can check Here.
So under the webapp folder, create one more zul file and name as login.zul and under the webapp->css folder, create one more css file as login.css as shown.
You can copy the login.zul and login.css source from the download which is available at the bottom of this post. Please don’t forget to add this new css file in our ZKAddon.xml as shown here.
<?xml version="1.0" encoding="UTF-8"?>
<language-addon>
<addon-name>myaddon</addon-name>
<language-name>xul/html</language-name>
<stylesheet href="/css/login.css" type="text/css" />
<stylesheet href="/css/style.css" type="text/css" />
<stylesheet href="/css/ThemeStyle.css" type="text/css" />
</language-addon>
Step 5:
Next we will need to update our dao and service layer by adding one more method to execute named query.
CRUDDao.
<T> T GetUniqueEntityByNamedQuery(String query, Object... params);
CRUDDaoImpl.java
@SuppressWarnings("unchecked")
public <T> T GetUniqueEntityByNamedQuery(String query, Object... params) {
Query q = getCurrentSession().getNamedQuery(query);
int i = 0;
for (Object o : params) {
q.setParameter(i, o);
}
List<T> results = q.list();
T foundentity = null;
if (!results.isEmpty()) {
// ignores multiple results
foundentity = results.get(0);
}
return foundentity;
}
CRUDService.java
<T> T GetUniqueEntityByNamedQuery(String query, Object... params);
CRUDServiceImpl.java
@Transactional
public <T> T GetUniqueEntityByNamedQuery(String query, Object... params) {
return CRUDDao.GetUniqueEntityByNamedQuery(query, params);
}
In the domain object, let us add the named query in the top
UserProfile.java
@Entity
@Table(name = "userprofile")
@NamedQuery(name = "UserProfile.findUserByUserID", query = "SELECT usr FROM UserProfile as usr WHERE usr.userLoginID = ?")
public class UserProfile implements Serializable {
------Other Code-----
Step 6:
Next we will create our custom authentication by extending spring UserDetailsService interface. Select the “service” package, right click, select new->Other->Class and enter the class name as “MyUserDetailsService”.
package zkexample.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import zkexample.domain.UserProfile;
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private CRUDService CRUDService;
/*
* You just have to make sure that the user-by-username-query returns three
* fields. 1) the userName 2) the password 3) boolean for is the user
* active. If you don't have an active field, make your query always return
* true for that third field.
*/
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
// Declare a null Spring User
UserDetails user = null;
try {
// Search database for a user that matches the specified username
// You can provide a custom DAO to access your persistence layer
// Or use JDBC to access your database
// DbUser is our custom domain user. This is not the same as
// Spring's User
UserProfile dbUser = CRUDService.GetUniqueEntityByNamedQuery(
"UserProfile.findUserByUserID", username);
// Populate the Spring User object with details from the dbUser
// Here we just pass the username, password, and access level
// getAuthorities() will translate the access level to the correct
// role type
user = new User(dbUser.getUserLoginID(), dbUser.getPassword()
.toLowerCase(), true, true, true, true,
getAuthorities());
} catch (Exception e) {
throw new UsernameNotFoundException("Error in retrieving user");
}
// Return user to Spring for processing.
// Take note we're not the one evaluating whether this user is
// authenticated or valid
// We just merely retrieve a user that matches the specified username
return user;
}
/**
* Retrieves the correct ROLE type depending on the access level, where
* access level is an Integer. Basically, this interprets the access value
* whether it's for a regular user or admin.
*
* @param access
* an integer value representing the access of the user
* @return collection of granted authorities
*/
public Collection<GrantedAuthority> getAuthorities() {
// Create a list of grants for this user
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);
// All users are granted with ROLE_USER access
// Therefore this user gets a ROLE_USER by default
authList.add(new GrantedAuthorityImpl("ROLE_USER"));
// User has admin access
authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
// Return list of granted authorities
return authList;
}
}
Step 7:
Next we will add logout option in the userlist.zul file as follows. Just add one more button next add new button
<div style="float:right">
<button label="Add New" onClick="@command('onAddNew')"
mold="trendy" sclass="mybutton button blue small" />
<button label="Logout" mold="trendy" id="btnlogout"
sclass="mybutton button blue small" onClick="@command('Logout')" />
</div>
And also, add the following code in the VM.
@Command
public void Logout()
{
Executions.sendRedirect("/j_spring_security_logout");
}
Now you select the Project->Right Click –>Select Run as –> Run on Server.
Video Demo
http://screencast.com/t/HXSul1abLlf
In the next part 7, We will add some more features to our listing page.
You can download the source here.