Struts 2 – Spring Security – Hibernate Custom Form Example

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.

spring_security_table_values

 

 

 

 

 

 

This is the table structure

spring_security_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

directory_structure_ssh

 

 

 

 

 

 

 

 

 

 

 

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

login_ssh_new

2. Login as “ddkr”, an authorized user

login_before_ddkr_ssh_new

login_after_ddkr

3. Logout

logout_ssh_new

4. Login as “arun”, an unauthorized user

login_before_arun_ssh_new

login_after_arun_ssh_new

5. Login as a non-existing user

login_before_invalid_ssh_new

login_invalid_ssh_new

Thanks.