Hi All,
In the previous post, we have seen how to set up the Spring Security with Struts2 forms. In this example we are going to see how to integrate Hibernate framework with Spring Security. Let’s move on to the example.
BEHIND THE SCENE
- NetBeans IDE 7.1.1
- Spring-Core-3.2.1.RELEASE
- Spring-Security-3.2.1.RELEASE
- Struts-core-2.3.15.3
- Hibernate 3
- JDK 1.7
- Apache Tomcat 7.0.22.0
- Microsoft SQL Server 2012
IMPLEMENTATION STEPS
Here are the steps to be followed for implementing spring security in our application.
1. web.xml
Here, we have to enable spring support for our application by adding a listener in the deployment descriptor. Also we have added spring security filter and struts2 filter.
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-database.xml /WEB-INF/spring-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> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>/redirect.jsp</welcome-file> </welcome-file-list> </web-app>
2. spring-database.xml
It depicts the custom user service. We represent our custom user service as a spring bean with an id “userDetailsService”. Spring creates an instance of this bean in the spring context when the application initializes. Please note that the file has been added to web.xml (line no. 7).
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="userDetailsService" class="com.ssh.service.UserService"/> </beans>
3. spring-security.xml
It describes the custom login form, “daoAuthenticationProvider” and “authenticationManager”. This file has also been added to web.xml (line no. 8).
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <http auto-config="true"> <intercept-url pattern="/admin" access="ROLE_ADMIN" /> <access-denied-handler error-page="/WEB-INF/Content/403.jsp" /> <form-login login-page="/login" default-target-url="/admin" authentication-failure-url="/login?error" username-parameter="username" password-parameter="password" /> <logout logout-success-url="/login?logout" /> </http> <beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:property name="userDetailsService" ref="userDetailsService"/> </beans:bean> <beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <beans:property name="providers"> <beans:list> <beans:ref local="daoAuthenticationProvider" /> </beans:list> </beans:property> </beans:bean> <authentication-manager erase-credentials="false"> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> </authentication-manager> </beans:beans>
3. UserService.java
This is our user service class.
package com.ssh.service; import com.ssh.dao.UserDao; import com.ssh.entity.Role; import com.ssh.entity.User; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import javax.persistence.EntityNotFoundException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; public class UserService implements UserDetailsService { UserDao dao = new UserDao(); @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = null; try { user = dao.findUser(username); if (user == null) { throw new UsernameNotFoundException("user not found"); } } catch (Exception e) { throw new EntityNotFoundException(e.getCause().getMessage()); } String uname = user.getUsername(); String password = user.getPassword(); boolean enabled = user.getEnabled(); boolean accountNonExpired = Boolean.TRUE; boolean credentialsNonExpired = Boolean.TRUE; boolean accountNonLocked = Boolean.TRUE; Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); for (Iterator it = user.getRoles().iterator(); it.hasNext();) { Role role = (Role) it.next(); authorities.add(new SimpleGrantedAuthority(role.getName())); } return new org.springframework.security.core.userdetails.User( uname, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); } }
4. UserDao.java
The DAO class.
package com.ssh.dao; import com.ssh.entity.User; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; public class UserDao { SessionFactory sessionFactory = null; Session session = null; public User findUser(String username) throws Exception { try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); session = sessionFactory.openSession(); User user = (User) session.createQuery("from User where username=:username").setParameter("username", username).uniqueResult(); return user; } catch (Exception e) { throw new Exception(e); } finally { session.flush(); session.close(); } } }
In this example, we need two entities namely “Role” and “User” which in turn creates two relational database tables “role” and “users” using Hibernate framework. A separate join table namely “user_roles” is also created .
5. Role.java
Here’s the role entity.
package com.ssh.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Role implements Serializable { private Long id; private String name; @Id @GeneratedValue @Column(name = "roleId") public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
6. User.java
The user entity.
package com.ssh.entity; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.*; @Entity @Table(name = "users") public class User implements Serializable { private Long id; private String username, password; private Boolean enabled; private List<Role> roles = new ArrayList<Role>(); @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinTable(name = "user_roles", joinColumns = { @JoinColumn(name = "userId"),}, inverseJoinColumns = { @JoinColumn(name = "roleId")}) public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public Boolean getEnabled() { return enabled; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public void setId(Long id) { this.id = id; } @Id @GeneratedValue @Column(name = "userId") public Long getId() { return id; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
Now we persist users and roles by using the following code snippet.
public void saveRole() throws Exception { try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); session = sessionFactory.openSession(); session.getTransaction().begin(); Role role = new Role(); role.setName("ROLE_ADMIN"); session.save(role); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } } public void saveUser() throws Exception { try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); session = sessionFactory.openSession(); session.getTransaction().begin(); User user = new User(); user.setUsername("ddkr"); user.setPassword("1986"); Role role = (Role) session.get(Role.class, 1L); List<Role> roles = new ArrayList<Role>(); roles.add(role); user.setRoles(roles); session.save(user); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } } public void saveRole() throws Exception { try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); session = sessionFactory.openSession(); session.getTransaction().begin(); Role role = new Role(); role.setName("ROLE_MANAGER"); session.save(role); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } } public void saveUser() throws Exception { try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); session = sessionFactory.openSession(); session.getTransaction().begin(); User user = new User(); user.setUsername("arun"); user.setPassword("1234"); Role role = (Role) session.get(Role.class, 2L); List<Role> roles = new ArrayList<Role>(); roles.add(role); user.setRoles(roles); session.save(user); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } }
The generated sql and table structure is given below.
USE [Spring] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Role]( [roleId] [numeric](19, 0) IDENTITY(1,1) NOT NULL, [name] [varchar](255) NULL, PRIMARY KEY CLUSTERED ( [roleId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO USE [Spring] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[users]( [userId] [numeric](19, 0) IDENTITY(1,1) NOT NULL, [enabled] [tinyint] NULL, [password] [varchar](255) NULL, [username] [varchar](255) NULL, PRIMARY KEY CLUSTERED ( [userId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO USE [Spring] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[user_roles]( [userId] [numeric](19, 0) NOT NULL, [roleId] [numeric](19, 0) NOT NULL, UNIQUE NONCLUSTERED ( [roleId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[user_roles] WITH CHECK ADD CONSTRAINT [FK73429949537304F6] FOREIGN KEY([roleId]) REFERENCES [dbo].[Role] ([roleId]) GO ALTER TABLE [dbo].[user_roles] CHECK CONSTRAINT [FK73429949537304F6] GO ALTER TABLE [dbo].[user_roles] WITH CHECK ADD CONSTRAINT [FK7342994958C85A60] FOREIGN KEY([userId]) REFERENCES [dbo].[users] ([userId]) GO ALTER TABLE [dbo].[user_roles] CHECK CONSTRAINT [FK7342994958C85A60] GO
This is how the tables look like.
This is the table structure
7. struts.xml
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="default" extends="struts-default" namespace="/"> <action name="admin" class="com.ssh.actions.AdminAction" method="execute"> <result name="success">/WEB-INF/Content/admin.jsp</result> </action> <action name="login"> <result>/WEB-INF/Content/login.jsp</result> </action> </package> </struts>
8. hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property> <property name="hibernate.connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property> <property name="hibernate.connection.url">jdbc:sqlserver://localhost:1433;databaseName=Spring</property> <property name="hibernate.connection.username">username</property> <property name="hibernate.connection.password">password</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.hbm2ddl.auto">update</property> <mapping class="com.ssh.entity.Role"/> <mapping class="com.ssh.entity.User"/> </session-factory> </hibernate-configuration>
9. AdminAction.java
package com.ssh.actions; import com.opensymphony.xwork2.ActionSupport; import java.util.Collection; import java.util.Iterator; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; public class AdminAction extends ActionSupport { private String username; private String password; @Override public String execute() { //Principal principal = ServletActionContext.getRequest().getUserPrincipal(); UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); System.out.println("username: " + userDetails.getUsername()); System.out.println("password: " + userDetails.getPassword()); Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>) userDetails.getAuthorities(); for (Iterator it = authorities.iterator(); it.hasNext();) { SimpleGrantedAuthority authority = (SimpleGrantedAuthority) it.next(); System.out.println("Role: " + authority.getAuthority()); } return SUCCESS; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } }
Next we move to the UI part.
10. redirect.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <META HTTP-EQUIV="Refresh" CONTENT="0;URL=login">
This JSP page has been set as welcome page in the deployment descriptor. When this page is loaded in the browser, the URL gets redirected to “login” action.
11. login.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags" %> <!DOCTYPE html> <html> <body> <h1>Struts2 - Spring Security Demo</h1> <s:if test="%{#parameters.error != null}"> <div style="color: red">Invalid User</div> </s:if> <s:form name="loginForm" action="j_spring_security_check" method="post"> <s:textfield name="username" label="Username"/> <s:password name="password" label="Password"/> <s:submit value="Login"/> </s:form> </body> </html>
12. admin.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@page session="true"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Admin Page</title> </head> <body> <h1>Hello ${pageContext.request.userPrincipal.name}</h1> <a href="j_spring_security_logout">Logout</a> </body> </html>
13. 403.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Access Denied</title> </head> <body> <h1>Access Denied for ${pageContext.request.userPrincipal.name}!!!</h1> <a href="login">Back</a> </body> </html>
DIRECTORY STRUCTURE
The above said example says, the access to the URL pattern “/admin” is restricted only to the users with role name “ROLE_ADMIN”. Then we create a user with username “ddkr” and password “1986” and assign him the role “ROLE_ADMIN”. So obviously he can access the URL pattern “/admin”. We have also created another user “arun” who is having “ROLE_MANAGER” role. When “arun” tries to access “/admin” URL, he is redirected to access-deny page. When some other unauthorized users try to access the “/admin” URL, the spring security tries to authenticate them but gets failed and they are redirected to the login page again.
SCREEN SHOTS
1. Login Screen
2. Login as “ddkr”, an authorized user
3. Logout
4. Login as “arun”, an unauthorized user
5. Login as a non-existing user
Thanks.