Spring MVC: Security with MySQL and Hibernate
Spring has a lot of different modules. All of them are useful for the concrete purposes. Today I’m going to talk about Spring Security. This module provides flexible approach to manage permitions for access to different parts of web-application. In the post I’ll examine integration of Spring MVC, Hibernate, MySQL with Spring Security.
A regular case for any web-application is separation of functionality between some user groups. E.g. user with a “moderator” role can edit existing records in a database. An user
with “admin” role can do the same thing as the user with “moderator” role plus create new records. In Spring MVC application permition management can be implemented with the Spring Security.
The goal
As an example I will use sample Spring MVC application with Hibernate. The users and their roles will be stored in a database. MySQL will be used as the database. I’m going to create three tables: users, roles, user_roles. As you might guess the user_roles table is an intermediary table. In the application will be two roles: moderator and admin. There will be several pages with access for the moderator and for the admin.
Preparation
In order to make Spring Security available in a project, just add following dependencies in a pom.xml file:
<!-- Spring Security --> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-core</artifactid> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-web</artifactid> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-config</artifactid> <version>3.1.3.RELEASE</version> </dependency>
I have to create three tables in the database and insert several records there.
CREATE TABLE `roles` ( `id` int(6) NOT NULL AUTO_INCREMENT, `role` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; CREATE TABLE `users` ( `id` int(6) NOT NULL AUTO_INCREMENT, `login` varchar(20) NOT NULL, `password` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; CREATE TABLE `user_roles` ( `user_id` int(6) NOT NULL, `role_id` int(6) NOT NULL, KEY `user` (`user_id`), KEY `role` (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And here is a code for the roles and users:
INSERT INTO hibnatedb.roles (role) VALUES ('admin'), ('moderator'); INSERT INTO hibnatedb.users (login, password) VALUES ('moder', '111111'), ('adm', '222222'); INSERT INTO hibnatedb.user_roles (user_id, role_id) VALUES (1, 2), (2, 1);
Main part
The complete structure of project has the following structure:
Since you can find this project on GitHub, I’ll omit some things which are out of the current theme. I want to start from the heart of every web-project, I mean web.xml file. Spring Security is based on simple filters, so I need to add declaration of the filter in the deployment descriptor:
... <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> ...
Now it’s time to create entities for the users and roles tables:
@Entity @Table(name="users") public class User { @Id @GeneratedValue private Integer id; private String login; private String password; @OneToOne(cascade=CascadeType.ALL) @JoinTable(name="user_roles", joinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")}, inverseJoinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")} ) private Role role; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
And
@Entity @Table(name="roles") public class Role { @Id @GeneratedValue private Integer id; private String role; @OneToMany(cascade=CascadeType.ALL) @JoinTable(name="user_roles", joinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")}, inverseJoinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")} ) private Set userRoles; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public Set getUserRoles() { return userRoles; } public void setUserRoles(Set userRoles) { this.userRoles = userRoles; } }
Each entity class requires DAO and Service layer.
public interface UserDAO { public User getUser(String login); }
And
@Repository public class UserDAOImpl implements UserDAO { @Autowired private SessionFactory sessionFactory; private Session openSession() { return sessionFactory.getCurrentSession(); } public User getUser(String login) { List userList = new ArrayList(); Query query = openSession().createQuery("from User u where u.login = :login"); query.setParameter("login", login); userList = query.list(); if (userList.size() > 0) return userList.get(0); else return null; } }
Respectively for the Role class:
public interface RoleDAO { public Role getRole(int id); }
And
@Repository public class RoleDAOImpl implements RoleDAO { @Autowired private SessionFactory sessionFactory; private Session getCurrentSession() { return sessionFactory.getCurrentSession(); } public Role getRole(int id) { Role role = (Role) getCurrentSession().load(Role.class, id); return role; } }
The same pairs for the service layer:
public interface UserService { public User getUser(String login); }
And
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; public User getUser(String login) { return userDAO.getUser(login); } }
Respectively for the Role class:
public interface RoleService { public Role getRole(int id); }
And
@Service @Transactional public class RoleServiceImpl implements RoleService { @Autowired private RoleDAO roleDAO; public Role getRole(int id) { return roleDAO.getRole(id); } }
Everything above was just mechanical, routine code. Now let’s work on the Spring Security code. In order to plug in Spring Security into the project I have to create CustomUserDetailsService class and implement UserDetailsService interface.
import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; 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 org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.sprsec.dao.UserDAO; @Service @Transactional(readOnly=true) public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserDAO userDAO; public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException { com.sprsec.model.User domainUser = userDAO.getUser(login); boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; return new User( domainUser.getLogin(), domainUser.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(domainUser.getRole().getId()) ); } public Collection getAuthorities(Integer role) { List authList = getGrantedAuthorities(getRoles(role)); return authList; } public List getRoles(Integer role) { List roles = new ArrayList(); if (role.intValue() == 1) { roles.add("ROLE_MODERATOR"); roles.add("ROLE_ADMIN"); } else if (role.intValue() == 2) { roles.add("ROLE_MODERATOR"); } return roles; } public static List getGrantedAuthorities(List roles) { List authorities = new ArrayList(); for (String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); } return authorities; } }
The main purpose of the class is to map User class of the application to the User class of Spring Security. This is one of the killer-feature of the Spring Security. In this way you can adapt any kind of Spring MVC application to usage of the Security module.
Controllers and Views
One of the most frequent question regarding Spring Security is how to create a custom login form. The answer is simple enough. You need to create a JSP file with a the form, and specify there action attribute ().
The most part of the URL-mapping depends on spring-security.xml file:
... <http auto-config="true"> <intercept-url pattern="/sec/moderation.html" access="ROLE_MODERATOR"> <intercept-url pattern="/admin/*" access="ROLE_ADMIN"> <form-login login-page="/user-login.html" default-target-url="/success-login.html" authentication-failure-url="/error-login.html"> <logout logout-success-url="/index.html"> </logout></form-login></intercept-url></intercept-url></http> <authentication-manager> <authentication-provider user-service-ref="customUserDetailsService"> <password-encoder hash="plaintext"> </password-encoder></authentication-provider> </authentication-manager> ...
As you can see, I specified URLs for the: login page, default page after success login, error page for the situations when credentials are invalid. Also I declared URLs which require some access permitions. And the most important thing is a declaration of the authentication-manager. Through this Spring Security will use database to identify users and their roles.
Controllers:
@Controller public class LinkNavigation { @RequestMapping(value="/", method=RequestMethod.GET) public ModelAndView homePage() { return new ModelAndView("home"); } @RequestMapping(value="/index", method=RequestMethod.GET) public ModelAndView indexPage() { return new ModelAndView("home"); } @RequestMapping(value="/sec/moderation", method=RequestMethod.GET) public ModelAndView moderatorPage() { return new ModelAndView("moderation"); } @RequestMapping(value="/admin/first", method=RequestMethod.GET) public ModelAndView firstAdminPage() { return new ModelAndView("admin-first"); } @RequestMapping(value="/admin/second", method=RequestMethod.GET) public ModelAndView secondAdminPage() { return new ModelAndView("admin-second"); } }
And
@Controller public class SecurityNavigation { @RequestMapping(value="/user-login", method=RequestMethod.GET) public ModelAndView loginForm() { return new ModelAndView("login-form"); } @RequestMapping(value="/error-login", method=RequestMethod.GET) public ModelAndView invalidLogin() { ModelAndView modelAndView = new ModelAndView("login-form"); modelAndView.addObject("error", true); return modelAndView; } @RequestMapping(value="/success-login", method=RequestMethod.GET) public ModelAndView successLogin() { return new ModelAndView("success-login"); } }
Views you can see on GitHub.
Pay you attention to adding of @ImportResource(“classpath:spring-security.xml”) in the WebAppConfig java class.
Summary
I think this article will help you to dive into Spring Security. I used here Hibernate and MySQL since such combination of technologies isn’t used often in other tutorials in the internet. Probably you noticed that I used some XMLs in the project, that’s because currently there is no ways to implement all these stuff using annotation based approach.
Hi,
nice example. I was going through your project from git and would like to ask why there is twice componentscan : 1. in RootConfig and 2. in WebAppConfig.
thank you
Petr F.
When i am trying to launch this project i get FAIL – Deployed application at context path /security-spr but context failed to start.
I do not know what is wrong.
Please help
Hi , How can i found this project on the git.
Can you post the web.xml so we can run the app?
thanks bro
hi…please mail me this application to my mai id mahadev.hallikhed130@gmail.com