Code ví dụ Spring Security Hibernate 5 + Database MySQL
(Xem lại: Code ví dụ Spring Hibernate, Code ví dụ Spring Hibernate Transaction.)
Ở bài này mình sẽ thực hiện ví dụ sử dụng Spring Hibernate kết nối với MySQL để xác thực với Spring Security.
Các công nghệ sử dụng:
- Spring 5.0.2.RELEASE
- Spring Security 5.0.2.RELEASE
- Hibernate 5.2.12.Final
- Maven
- Tomcat
- JDK 1.8
- Eclipse + Spring Tool Suite
Tạo Database
Tạo database “spring_security
” spring_security.sql
Tạo table users
chứa thông tin username, password và enabled (enabled = 1 tức là account đã được active)
CREATE TABLE `spring-security`.`users` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(45) NULL, `password` VARCHAR(255) NULL, `enabled` INT(1) NULL, PRIMARY KEY (`id`), UNIQUE INDEX `username_UNIQUE` (`username` ASC));
Tạo table role
chứa thông tin các role.
CREATE TABLE `spring_security`.`role` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));
Tạo table users_roles
thực hiện mapping để biết user nào có những role nào
CREATE TABLE `spring_security`.`users_roles` ( `id` INT NOT NULL AUTO_INCREMENT, `user` INT NULL, `role` INT NULL, PRIMARY KEY (`id`), INDEX `_idx` (`user` ASC), INDEX `dfdf_idx` (`role` ASC), CONSTRAINT `user` FOREIGN KEY (`user`) REFERENCES `spring-security`.`users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `role` FOREIGN KEY (`role`) REFERENCES `spring-security`.`role` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION);
Tạo 2 tài khoản kai/123456 và sena/123456 (ở đây mình mã hóa mật khẩu bằng bcypt)
INSERT INTO `users` VALUES (1,'kai','$2a$04$GYGsaJj9l6kH2GikK6QVzO0v3sOCxt3vdkiA2/tcoSw8erI85ZYDG',1),(2,'sena','$2a$04$GYGsaJj9l6kH2GikK6QVzO0v3sOCxt3vdkiA2/tcoSw8erI85ZYDG',1);
Tạo 2 role là ROLE_ADMIN và ROLE_USER
INSERT INTO `role` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER');
Thiết lập role = “ROLE_ADMIN”, “ROLE_USER” cho tài khoản ‘kai’, và role = “ROLE_USER” cho tài khoản ‘sena’
INSERT INTO `users_roles` VALUES (1,1,1),(2,1,2),(4,2,2);
Tạo Maven Project
Thư viện sử dụng
<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>stackjava.com</groupId> <artifactId>SpringSecurityHibernate</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <spring.version>5.0.2.RELEASE</spring.version> <spring.security.version>5.0.2.RELEASE</spring.security.version> <hibernate.version>5.2.12.Final</hibernate.version> <jstl.version>1.2</jstl.version> </properties> <dependencies> <!-- Spring Web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring ORM --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Security --> <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> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <!-- JSP - Servlet Lib --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- jstl for jsp page --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency> </dependencies> </project>
File web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>SpringSecurityHibernate</display-name> <!-- SPRING MVC --> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Loads Spring Security config file --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-mvc-servlet.xml, /WEB-INF/spring-security.xml </param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <!-- Spring Security --> <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> </web-app>
File cấu hình spring mvc
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:component-scan base-package="stackjava.com.springsecurityhibernate" /> <mvc:annotation-driven /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/views/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/spring_security" /> <property name="username" value="root" /> <property name="password" value="admin1234" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="packagesToScan" value="stackjava.com.springsecurityhibernate.entities" /> <property name="hibernateProperties" value="classpath:hibernate.properties" /> </bean> </beans>
File cấu hình spring security
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http auto-config="true"> <intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" /> <intercept-url pattern="/user**" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')" /> <form-login login-page="/login" login-processing-url="/j_spring_security_login" default-target-url="/user" authentication-failure-url="/login?error" username-parameter="username" password-parameter="password" /> <logout logout-url="/j_spring_security_logout" logout-success-url="/logout" delete-cookies="JSESSIONID" /> </http> <authentication-manager> <authentication-provider user-service-ref="myUserDetailsService"> <password-encoder hash="bcrypt"/> </authentication-provider> </authentication-manager> </beans:beans>
Ở trên mình sử dụng bean ‘myUserDetailsService
‘ để thực hiện việc xác thực mà mã hóa mật khẩu bằng bcrypt
File thuộc tính hibernate
hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.hibernate.current_session_context_class=thread hibernate.show_sql=true
File controller
package stackjava.com.springsecurityhibernate.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class BaseController { @RequestMapping(value = { "/login", "/" }) public String login(@RequestParam(value = "error", required = false) final String error, final Model model) { if (error != null) { model.addAttribute("message", "Login Failed!"); } return "login"; } @RequestMapping("/admin") public String admin() { return "admin"; } @RequestMapping("/user") public String user() { return "user"; } @RequestMapping("/logout") public String logout(final Model model) { model.addAttribute("message", "Logged out!"); return "login"; } }
Các file entities
package stackjava.com.springsecurityhibernate.entities; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import static javax.persistence.GenerationType.IDENTITY; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "role", catalog = "spring_security") public class Role implements java.io.Serializable { private static final long serialVersionUID = 1L; private Integer id; private String name; private Set<UsersRoles> usersRoleses = new HashSet<UsersRoles>(0); public Role() { } public Role(String name, Set<UsersRoles> usersRoleses) { this.name = name; this.usersRoleses = usersRoleses; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "id", unique = true, nullable = false) public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } @Column(name = "name", length = 45) public String getName() { return this.name; } public void setName(String name) { this.name = name; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "role") public Set<UsersRoles> getUsersRoleses() { return this.usersRoleses; } public void setUsersRoleses(Set<UsersRoles> usersRoleses) { this.usersRoleses = usersRoleses; } }
package stackjava.com.springsecurityhibernate.entities; import static javax.persistence.GenerationType.IDENTITY; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @Entity @Table(name = "users", catalog = "spring_security", uniqueConstraints = @UniqueConstraint(columnNames = "username")) public class User implements java.io.Serializable { private static final long serialVersionUID = 1L; private Integer id; private String username; private String password; private Boolean enabled; private Set<UsersRoles> usersRoleses = new HashSet<UsersRoles>(0); public User() { } public User(final String username, final String password, final Boolean enabled, final Set<UsersRoles> usersRoleses) { this.username = username; this.password = password; this.enabled = enabled; this.usersRoleses = usersRoleses; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "id", unique = true, nullable = false) public Integer getId() { return this.id; } public void setId(final Integer id) { this.id = id; } @Column(name = "username", unique = true, length = 45) public String getUsername() { return this.username; } public void setUsername(final String username) { this.username = username; } @Column(name = "password") public String getPassword() { return this.password; } public void setPassword(final String password) { this.password = password; } @Column(name = "enabled", nullable = false, columnDefinition = "TINYINT(1)") public Boolean getEnabled() { return this.enabled; } public void setEnabled(final Boolean enabled) { this.enabled = enabled; } @OneToMany(fetch = FetchType.EAGER, mappedBy = "users") public Set<UsersRoles> getUsersRoleses() { return this.usersRoleses; } public void setUsersRoleses(final Set<UsersRoles> usersRoleses) { this.usersRoleses = usersRoleses; } @Transient public List<GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); for (UsersRoles usersRoles: this.usersRoleses) { authorities.add(new SimpleGrantedAuthority(usersRoles.getRole().getName())); } return authorities; } }
package stackjava.com.springsecurityhibernate.entities; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import static javax.persistence.GenerationType.IDENTITY; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name = "users_roles", catalog = "spring_security") public class UsersRoles implements java.io.Serializable { private static final long serialVersionUID = 1L; private Integer id; private Role role; private User users; public UsersRoles() { } public UsersRoles(Role role, User users) { this.role = role; this.users = users; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "id", unique = true, nullable = false) public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "role") public Role getRole() { return this.role; } public void setRole(Role role) { this.role = role; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user") public User getUsers() { return this.users; } public void setUsers(User users) { this.users = users; } }
File DAO
package stackjava.com.springsecurityhibernate.dao; import java.util.ArrayList; import java.util.List; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import stackjava.com.springsecurityhibernate.entities.User; @Repository(value = "userDAO") @Transactional(rollbackFor = Exception.class) public class UserDAO { @Autowired private SessionFactory sessionFactory; public User loadUserByUsername(final String username) { List<User> users = new ArrayList<User>(); Session session = this.sessionFactory.getCurrentSession(); users = session.createQuery("from User where username=?", User.class).setParameter(0, username).list(); if (users.size() > 0) { return users.get(0); } else { return null; } } }
File Service
package stackjava.com.springsecurityhibernate.service; import org.springframework.beans.factory.annotation.Autowired; 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 stackjava.com.springsecurityhibernate.dao.UserDAO; @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UserDAO userDAO; public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { stackjava.com.springsecurityhibernate.entities.User user = userDAO.loadUserByUsername(username); if (user == null) { return null; } boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; return new User(username, user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, user.getAuthorities()); } }
class MyUserDetailsService.java
dùng để xác thực nên nó sẽ cài đặt lại từ org.springframework.security.core.userdetails.UserDetailsService
Method loadUserByUsername
sẽ nhận đầu vào là giá trị username mà client nhập trên màn hình sau đấy tìm đối tượng User tương ứng trong database
- Nếu không tìm thấy thì trả về null (login failed)
- Nếu tìm thấy nó sẽ tạo một đối tượng
org.springframework.security.core.userdetails.User
tương ứng và kiểm tra password có đúng với password đã nhập không? tài khoản có enabled không? có quyền không?…
Các file view.
Các file view:
Trang login
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>login</title> </head> <body> <h1>Spring MVC 5 - Spring Security 5 - Hibernate 5</h1> <h2>${message}</h2> <form name='loginForm' action="<c:url value='j_spring_security_login' />" method='POST'> <table> <tr> <td>User:</td> <td><input type='text' name='username'></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="login" /></td> </tr> </table> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> </form> </body> </html>
Trang admin, chỉ role = “ROLE_ADMIN” mới được truy cập
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>Admin Page</title> </head> <body> <h1>Admin Page</h1> <h2>Welcome: ${pageContext.request.userPrincipal.name}</h2> <form action="<c:url value="/j_spring_security_logout" />" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
Trang user, role = “ROLE_ADMIN” hoặc “ROLE_USER” có thể truy cập.
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>User Page</title> </head> <body> <h1>User Page</h1> <h2>Welcome: ${pageContext.request.userPrincipal.name}</h2> <a href="<c:url value="/admin" />">Admin Page</a> <br/> <form action="<c:url value="/j_spring_security_logout" />" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
Demo:
Login với tài khoản kai/123456
Truy cập trang admin.jsp
Logout.
Login với tài khoản sena/123456
Truy cập trang admin.jsp
Vì tài khoản sena/123456 không có role = “ROLE_ADMIN” nên không thể truy cập trang admin.jsp.
Code ví dụ Spring Security Hibernate 5 + Database MySQL stackjava.com
Okay, Done!
Download code ví dụ trên tại đây
References:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#ns-auth-providers