Code ví dụ Spring Security Hibernate 5 + Database MySQL

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:

Tạo Database

Tạo database “spring_security”  spring_security.sql

Spring Security Hibernate database

 

spring security database login

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

Code ví dụ Spring Security Hibernate 5 + Database MySQL

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

Code ví dụ Spring Security Hibernate 5 + Database MySQL

Truy cập trang admin.jsp

Code ví dụ Spring Security Hibernate 5 + Database MySQL

Logout.

Code ví dụ Spring Security Hibernate 5 + Database MySQL

Login với tài khoản sena/123456

Code ví dụ Spring Security Hibernate 5 + Database MySQL

Truy cập trang admin.jsp

Ví dụ spring security 5 + hibernate 5

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

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

stackjava.com