CÔNG NGHỆ THÔNG TIN >> SINH VIÊN BKAP

Bảo mật ứng dụng web với Spring Boot

Đăng lúc: 09:12 AM - 28/03/2025 bởi Charles Chung - 94

Trong bài này chúng ta sẽ tìm hiểu về bảo mật ứng dụng web, sau đó demo ví dụ đăng nhập và phân quyền sử dụng Security trong SpringBoot và Thymeleaf

1. Giới thiệu

Spring Security là một module trong Spring Framework, cung cấp các chức năng bảo mật cho các ứng dụng Java, đặc biệt là các ứng dụng Spring Boot. Nó giúp bảo vệ ứng dụng khỏi các mối đe dọa bảo mật như xâm nhập trái phép, tấn công SQL Injection, tấn công Cross-Site Scripting (XSS), và các cuộc tấn công CSRF (Cross-Site Request Forgery). sau đây là một số khái niệm trong bảo mật web.

Authentication: Xác thực là quá trình kiểm tra danh tính của người dùng. Spring Security hỗ trợ nhiều phương thức xác thực, bao gồm:

  • Xác thực với cơ sở dữ liệu (Database Authentication): Xác thực người dùng từ cơ sở dữ liệu.
  • Xác thực qua LDAP (LDAP Authentication): Sử dụng LDAP để xác thực người dùng.
  • Xác thực thông qua OAuth2, JWT: Xác thực sử dụng token OAuth2 hoặc JWT (JSON Web Token) để bảo mật API.
  • Xác thực cơ bản (Basic Authentication): Xác thực qua header HTTP.

Authorization: Phân quyền là quá trình quyết định người dùng có quyền truy cập vào các tài nguyên hay không. Spring Security hỗ trợ:

  • Role-based Authorization: Phân quyền theo vai trò của người dùng, như ADMIN, USER,...
  • Method-level Authorization: Phân quyền ở mức độ phương thức, sử dụng annotation như @PreAuthorize, @Secured, hay @RolesAllowed.
  • URL-based Authorization: Phân quyền theo URL hoặc yêu cầu HTTP.

CFRS Protection: Spring Security có cơ chế bảo vệ chống lại CSRF (Cross-Site Request Forgery) - một loại tấn công mà kẻ tấn công gửi các yêu cầu không mong muốn từ một trang web bên ngoài để thay đổi trạng thái của ứng dụng. Spring Security cung cấp một lớp bảo vệ CSRF mặc định, nhưng bạn cũng có thể tùy chỉnh hoặc tắt nếu ứng dụng của bạn chỉ cung cấp các API RESTful.

Session management: Spring Security cho phép quản lý phiên (session management), đảm bảo rằng không có nhiều phiên người dùng cùng lúc (để tránh giả mạo hoặc tấn công session hijacking).
Session Fixation Protection: Giúp bảo vệ ứng dụng khỏi các cuộc tấn công giả mạo phiên.
Concurrent Session Control: Giới hạn số lượng phiên đăng nhập đồng thời của một người dùng.

Bảo vệ API với JWT: Spring Security rất phổ biến trong các ứng dụng web RESTful. JWT (JSON Web Token) có thể được sử dụng để bảo mật các API. Người dùng sẽ đăng nhập và nhận một token, sau đó gửi token này trong mỗi yêu cầu HTTP tiếp theo. Spring Security sẽ xác thực token và đảm bảo quyền truy cập hợp lệ.

Bảo vệ các Request HTTP: Spring Security cung cấp cấu hình bảo vệ các yêu cầu HTTP, bao gồm:

  • Bảo vệ đường dẫn URL: Bạn có thể yêu cầu một số URL phải có quyền truy cập nhất định, ví dụ như chỉ cho phép người dùng có quyền ADMIN truy cập một URL nhất định.
  • Cấu hình HTTP Security: Sử dụng HttpSecurity để cấu hình các quy tắc bảo mật cho ứng dụng của bạn.

Mã hóa mật khẩu: Spring Security sử dụng các thuật toán mã hóa mạnh mẽ như BCrypt, PBKDF2, và SCrypt để lưu trữ mật khẩu người dùng một cách an toàn.

OAuth 2.0 và OpenID Connect: Spring Security tích hợp tốt với các giao thức bảo mật hiện đại như OAuth 2.0 và OpenID Connect, cho phép xác thực và phân quyền với các nhà cung cấp bên thứ ba như Google, Facebook, hoặc các nhà cung cấp OAuth.

Cấu hình bảo mật linh hoạt: Bạn có thể tùy chỉnh các cấu hình bảo mật trong ứng dụng Spring Boot thông qua file application.properties hoặc application.yml để thay đổi các thông số bảo mật mà không cần thay đổi mã nguồn.

Tích hợp với Spring Boot Actuator: Spring Security có thể kết hợp với Spring Boot Actuator để bảo mật các endpoints của Actuator, tránh việc người dùng không có quyền truy cập vào các thông tin nhạy cảm của ứng dụng.

2. Ví dụ demo

Trong ví dụ này chúng ta sẽ sử dụng thư viện security của spring boot và thymeleaf, thông tin người dùng được lưu trong SQL Server, người dùng được chia làm 2 role: admin-> được truy cập vào bất kỳ đâu, customer-> truy cập trên trang khác hàng. Đối với trang admin buộc người dùng phải login mới quy cập được. Chúng ta tiếp tục với ví dụ bài trước (xem tại đây)

Bước 1: Mở project trước và bổ sung thêm các class và view như cấu trúc bên dưới

Bước 2: Mở file pom.xml bổ sung các dependency bên dưới nếu chưa có

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity6</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect -->
		<dependency>
		    <groupId>nz.net.ultraq.thymeleaf</groupId>
		    <artifactId>thymeleaf-layout-dialect</artifactId>
		</dependency>

Bước 3: Bổ sung phần view Login.html và 403.html (lấy từ template trang chủ ở bài trước)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout"
	layout:decorate="~{home_layout}">
<head>
<meta charset="UTF-8">
<title>Đăng nhập</title>
</head>
<body>
	<div layout:fragment="page_content">
		<!--Page Title-->
		<div class="page section-header text-center">
			<div class="page-title">
				<div class="wrapper">
					<h1 class="page-width">Login</h1>
				</div>
			</div>
		</div>
		<!--End Page Title-->

		<div class="container">
			<div class="row">
				<div class="col-12 col-sm-12 col-md-6 col-lg-6 main-col offset-md-3">
					<div class="mb-4">
						<form method="post" action="/login" id="CustomerLoginForm"
							accept-charset="UTF-8" class="contact-form">
							<div class="row">
								<div class="col-12 col-sm-12 col-md-12 col-lg-12">
									<div style="color: red" th:if="${param.deny}">Bạn không
										có quyền truy cập hoặc không tìm thấy!</div>
									<div style="color: red" th:if="${param.error}">
										Sai tên đăng nhập hoặc mật khẩu <span th:text="${param.error}"></span>
									</div>
									<div style="color: blue" th:if="${param.logout}">Bạn vừa
										thoát</div>
									<div class="form-group">
										<label for="CustomerEmail">Username</label> <input
											type="text" name="username" placeholder=""
											id="CustomerEmail" class="" autocorrect="off"
											autocapitalize="off" autofocus="">
									</div>
								</div>
								<div class="col-12 col-sm-12 col-md-12 col-lg-12">
									<div class="form-group">
										<label for="CustomerPassword">Password</label> <input
											type="password" value="" name="password" placeholder=""
											id="CustomerPassword" class="">
									</div>
								</div>
							</div>
							<div class="row">
								<div class="text-center col-12 col-sm-12 col-md-12 col-lg-12">
									<input type="submit" class="btn mb-3" value="Sign In">
									<p class="mb-4">
										<a href="#" id="RecoverPassword">Forgot your password?</a>
										&nbsp; | &nbsp; <a href="register.html"
											id="customer_register_link">Create account</a>
									</p>
								</div>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

403.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Thông báo quyền hạn</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
</head>

<body>
    <div class="container">
        <img style="margin:auto;width:50%;display: block;" src="/assets/images/error-403-forbidden-access-hanam88.png" />
        <h4 style="text-align: center;">TRANG NÀY KHÔNG TỒN TẠI HOẶC BẠN KHÔNG CÓ QUYỀN TRUY CẬP! <a style="text-align: center;" href="javascript:history.back()">QUAY LẠI</a></h4>
       
    </div>
</body>

</html>

Bước 4: Xây dựng layout trang admin sử dụng template tải tại đây

- Copy tài nguyên vào thư mục static

- Code cho file admin_layout.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!doctype html>

<html lang="en" xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout"
	xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">

<head>
<meta charset="utf-8">
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title layout:title-pattern="$CONTENT_TITLE | $LAYOUT_TITLE">Quản
	trị web</title>
<link href="/assets/backend/vendor/fontawesome/css/fontawesome.min.css"
	rel="stylesheet">
<link href="/assets/backend/vendor/fontawesome/css/solid.min.css"
	rel="stylesheet">
<link href="/assets/backend/vendor/fontawesome/css/brands.min.css"
	rel="stylesheet">
<link href="/assets/backend/vendor/bootstrap/css/bootstrap.min.css"
	rel="stylesheet">
<link href="/assets/backend/css/master.css" rel="stylesheet">
<link href="/assets/backend/vendor/flagiconcss/css/flag-icon.min.css"
	rel="stylesheet">
</head>

<body>
	<div class="wrapper">
		<div th:replace="~{/backend/fragments/side_bar::sidebar}">Side
			bar</div>
		<div id="body" class="active">
			<!-- navbar navigation component -->
			<div th:replace="~{/backend/fragments/nav_bar::navbar}">Nav bar
			</div>
			<!-- end of navbar navigation -->
			<div layout:fragment="admin_content" class="content"></div>
		</div>
	</div>
	<script src="/assets/backend/vendor/jquery/jquery.min.js"></script>
	<script
		src="/assets/backend/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
	<script src="/assets/backend/vendor/chartsjs/Chart.min.js"></script>
	<script src="/assets/backend/js/dashboard-charts.js"></script>
	<script src="/assets/backend/js/script.js"></script>
</body>

</html>

- Code cho file backend/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{admin_layout}">
<head>
<meta charset="UTF-8">
<title>Trang chủ</title>
</head>
<body>
	<div layout:fragment="admin_content">
		<div class="container">
			Copy trong template vào đây
		</div>
	</div>
</body>
</html>

- Code cho file backend/nav_bar.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 <nav th:fragment="navbar" class="navbar navbar-expand-lg navbar-white bg-white">
                <button type="button" id="sidebarCollapse" class="btn btn-light">
                    <i class="fas fa-bars"></i><span></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="nav navbar-nav ms-auto">
                        <li class="nav-item dropdown">
                            <div class="nav-dropdown">
                                <a href="#" id="nav1" class="nav-item nav-link dropdown-toggle text-secondary" data-bs-toggle="dropdown" aria-expanded="false">
                                    <i class="fas fa-link"></i> <span>Quick Links</span> <i style="font-size: .8em;" class="fas fa-caret-down"></i>
                                </a>
                                <div class="dropdown-menu dropdown-menu-end nav-link-menu" aria-labelledby="nav1">
                                    <ul class="nav-list">
                                        <li><a href="" class="dropdown-item"><i class="fas fa-list"></i> Access Logs</a></li>
                                        <div class="dropdown-divider"></div>
                                        <li><a href="" class="dropdown-item"><i class="fas fa-database"></i> Back ups</a></li>
                                        <div class="dropdown-divider"></div>
                                        <li><a href="" class="dropdown-item"><i class="fas fa-cloud-download-alt"></i> Updates</a></li>
                                        <div class="dropdown-divider"></div>
                                        <li><a href="" class="dropdown-item"><i class="fas fa-user-shield"></i> Roles</a></li>
                                    </ul>
                                </div>
                            </div>
                        </li>
                        <li class="nav-item dropdown">
                            <div class="nav-dropdown">
                                <a href="#" id="nav2" class="nav-item nav-link dropdown-toggle text-secondary" data-bs-toggle="dropdown" aria-expanded="false">
                                    <i class="fas fa-user"></i> <span th:text="${#authentication.principal.fullName}">John Doe</span> <i style="font-size: .8em;" class="fas fa-caret-down"></i>
                                </a>
                                <div class="dropdown-menu dropdown-menu-end nav-link-menu">
                                    <ul class="nav-list">
                                        <li><a href="" class="dropdown-item"><i class="fas fa-address-card"></i> Profile</a></li>
                                        <li><a href="" class="dropdown-item"><i class="fas fa-envelope"></i> Messages</a></li>
                                        <li><a href="" class="dropdown-item"><i class="fas fa-cog"></i> Settings</a></li>
                                        <div class="dropdown-divider"></div>
                                        <li><a href="/logout" class="dropdown-item"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
                                    </ul>
                                </div>
                            </div>
                        </li>
                    </ul>
                </div>
</nav>

- Code cho file backend/side_bar.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<nav th:fragment="sidebar" id="sidebar">
            <div class="sidebar-header">
                <img src="/assets/backend/img/logo-bkap.png" alt="bootraper logo" class="app-logo">
            </div>
            <ul class="list-unstyled components text-secondary">
                <li>
                    <a href="/backend/products"><i class="fas fa-home"></i> Quản lý sản phẩm</a>
                </li>
                <li>
                    <a href="forms.html"><i class="fas fa-file-alt"></i> Forms</a>
                </li>
                <li>
                    <a href="tables.html"><i class="fas fa-table"></i> Tables</a>
                </li>
                <li>
                    <a href="charts.html"><i class="fas fa-chart-bar"></i> Charts</a>
                </li>
                <li>
                    <a href="icons.html"><i class="fas fa-icons"></i> Icons</a>
                </li>
                <li>
                    <a href="#uielementsmenu" data-bs-toggle="collapse" aria-expanded="false" class="dropdown-toggle no-caret-down"><i class="fas fa-layer-group"></i> UI Elements</a>
                    <ul class="collapse list-unstyled" id="uielementsmenu">
                        <li>
                            <a href="ui-buttons.html"><i class="fas fa-angle-right"></i> Buttons</a>
                        </li>
                        <li>
                            <a href="ui-badges.html"><i class="fas fa-angle-right"></i> Badges</a>
                        </li>
                        <li>
                            <a href="ui-cards.html"><i class="fas fa-angle-right"></i> Cards</a>
                        </li>
                        <li>
                            <a href="ui-alerts.html"><i class="fas fa-angle-right"></i> Alerts</a>
                        </li>
                        <li>
                            <a href="ui-tabs.html"><i class="fas fa-angle-right"></i> Tabs</a>
                        </li>
                        <li>
                            <a href="ui-date-time-picker.html"><i class="fas fa-angle-right"></i> Date & Time Picker</a>
                        </li>
                    </ul>
                </li>
                <li>
                    <a href="#authmenu" data-bs-toggle="collapse" aria-expanded="false" class="dropdown-toggle no-caret-down"><i class="fas fa-user-shield"></i> Authentication</a>
                    <ul class="collapse list-unstyled" id="authmenu">
                        <li>
                            <a href="login.html"><i class="fas fa-lock"></i> Login</a>
                        </li>
                        <li>
                            <a href="signup.html"><i class="fas fa-user-plus"></i> Signup</a>
                        </li>
                        <li>
                            <a href="forgot-password.html"><i class="fas fa-user-lock"></i> Forgot password</a>
                        </li>
                    </ul>
                </li>
                <li>
                    <a href="#pagesmenu" data-bs-toggle="collapse" aria-expanded="false" class="dropdown-toggle no-caret-down"><i class="fas fa-copy"></i> Pages</a>
                    <ul class="collapse list-unstyled" id="pagesmenu">
                        <li>
                            <a href="blank.html"><i class="fas fa-file"></i> Blank page</a>
                        </li>
                        <li>
                            <a href="404.html"><i class="fas fa-info-circle"></i> 404 Error page</a>
                        </li>
                        <li>
                            <a href="500.html"><i class="fas fa-info-circle"></i> 500 Error page</a>
                        </li>
                    </ul>
                </li>
                <li>
                    <a href="users.html"><i class="fas fa-user-friends"></i>Users</a>
                </li>
                <li>
                    <a href="settings.html"><i class="fas fa-cog"></i>Settings</a>
                </li>
            </ul>
        </nav>

- Code cho file backend/products/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!DOCTYPE html>
<html xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout" layout:decorate="~{admin_layout}">
<head>
<meta charset="UTF-8">
<title>Quản lý sản phẩm</title>
</head>
<body>
	<div layout:fragment="admin_content">
		<h1>Danh mục sản phẩm</h1>
	</div>
</body>
</html>

Bước 5: Code thêm một số lớp trong gói entities

- Enum ERole.java

1
2
3
4
5
6
7
package com.bkap.entities;

public enum ERole {
  ROLE_USER,
  ROLE_MODERATOR,
  ROLE_ADMIN
}

- Lớp Account.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.bkap.entities;

import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "accounts")
public class Account {
	@Id
	@Column(name = "accountid", columnDefinition = "nvarchar(36)")
	private String accountId;

	@NotBlank
	@Size(max = 64)
	@Column(name = "username", length = 64, unique = true)
	private String username;

	@NotBlank
	@Column(name = "password", length = 256)
	private String password;

	@Email
	@Column(name = "email", length = 64, unique = true)
	private String email;

	@Column(name = "phone", length = 64)
	private String phone;

	@Column(name = "fullname", columnDefinition = "nvarchar(100)")
	private String fullname;

	@Column(name = "address", columnDefinition = "nvarchar(256)")
	private String address;

	@Column(name = "picture", length = 512)
	private String picture;

	@Column(name = "enabled")
	private boolean enabled;

	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "accountroles", joinColumns = @JoinColumn(name = "accountid",columnDefinition = "nvarchar(36)"), inverseJoinColumns = @JoinColumn(name = "roleid"))
	private Set<Role> roles = new HashSet<>();

	public Account() {

	}

	public Account(String accountId, String username, String password,
			String email, String phone, String fullname, String address, String picture, boolean enabled) {
		this.accountId = accountId;
		this.username = username;
		this.password = password;
		this.email = email;
		this.phone = phone;
		this.fullname = fullname;
		this.address = address;
		this.picture = picture;
		this.enabled = enabled;
	}

	public String getAccountId() {
		return accountId;
	}

	public void setAccountId(String accountId) {
		this.accountId = accountId;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

	public String getFullname() {
		return fullname;
	}

	public void setFullname(String fullname) {
		this.fullname = fullname;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getPicture() {
		return picture;
	}

	public void setPicture(String picture) {
		this.picture = picture;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public Set<Role> getRoles() {
		return roles;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

}

- Lớp Role.Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.bkap.entities;

import jakarta.persistence.*;

@Entity
@Table(name = "roles")
public class Role {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="roleid")
	private Integer roleId;

	@Enumerated(EnumType.STRING)
	@Column(name="rolename",length = 20)
	private ERole roleName;

	public Role() {

	}

	public Role(Integer roleId, ERole roleName) {
		super();
		this.roleId = roleId;
		this.roleName = roleName;
	}

	public Integer getRoleId() {
		return roleId;
	}

	public void setRoleId(Integer roleId) {
		this.roleId = roleId;
	}

	public ERole getRoleName() {
		return roleName;
	}

	public void setRoleName(ERole roleName) {
		this.roleName = roleName;
	}

}

- Lớp AccountDetails.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.bkap.entities;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class AccountDetails implements UserDetails {
    private Collection<? extends GrantedAuthority> authorities;
    private String email;
    private String fullName;
    private String password;
    private String username;
    private String picture;
    private String phone;
    private String address;
    private boolean enabled;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;

   
    public AccountDetails(Collection<? extends GrantedAuthority> authorities, String email, String fullName,
            String password, String username, String picture, String phone, String address, boolean enabled,
            boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired) {
        this.authorities = authorities;
        this.email = email;
        this.fullName = fullName;
        this.password = password;
        this.username = username;
        this.picture = picture;
        this.phone = phone;
        this.address = address;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPicture() {
        return picture;
    }

    public void setPicture(String picture) {
        this.picture = picture;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }


    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    
}

Bước 6: Code lớp AccountRepository trong gói repositories

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.bkap.repositories;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.bkap.entities.Account;


@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
	Optional<Account> findByUsername(String username);

	Boolean existsByUsername(String username);

	Boolean existsByEmail(String email);
}

Bước 7: Code lớp AccountDetailService trong gói services

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.bkap.services;

import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.bkap.entities.Account;
import com.bkap.entities.AccountDetails;
import com.bkap.entities.ERole;
import com.bkap.entities.Role;
import com.bkap.repositories.AccountRepository;



@Service
public class AccountDetailService implements UserDetailsService {
    @Autowired
    AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Account> user = accountRepository.findByUsername(username);
        if (!user.isPresent())
            return null;
        Account acc=user.get();

        //xử lý lấy roles của người dùng đưa vào GrantedAuthority
        Collection<GrantedAuthority> grantedAuthoritySet = new HashSet<>();
        Set<Role> roles = acc.getRoles();
        for (Role userRole : roles) {
        	ERole rolename=userRole.getRoleName();
        	grantedAuthoritySet.add(new SimpleGrantedAuthority(rolename.name()));
		}
        //trả về đối tượng AccountDetails
        return new AccountDetails(grantedAuthoritySet, acc.getEmail(), acc.getFullname(), acc.getPassword(), acc.getUsername(), acc.getPicture(),acc.getPhone(),acc.getAddress(),  acc.isEnabled(),true,true,true);
    }
}

Bước 8: Code lớp WebConfigSecurity trong gói config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.bkap.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import com.bkap.services.AccountDetailService;

@Configuration
@EnableWebSecurity
public class WebConfigSecurity {
	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http.csrf((c) -> c.disable())
				.authorizeHttpRequests(authorize -> authorize
						.requestMatchers("/backend/**").hasRole("ADMIN")
						.requestMatchers("/**").permitAll()
						)
				.formLogin((formLogin) -> formLogin
						.usernameParameter("username")
						.passwordParameter("password")
						.loginPage("/login")
						.defaultSuccessUrl("/success", true)
						.failureUrl("/login?error").permitAll())
				.logout((logout) -> logout.logoutUrl("/logout").logoutSuccessUrl("/login?logout").permitAll())
				.exceptionHandling((ex) -> ex.accessDeniedPage("/403"));

		return http.build();
	}
	DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
		authProvider.setUserDetailsService(userDetailsService());
		authProvider.setPasswordEncoder(passwordEncoder());
		return authProvider;
	}
    UserDetailsService userDetailsService() {
		return new AccountDetailService();
	}
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

}

Bước 9: Code lớp HomeController trong gói client

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.bkap.controller.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.bkap.entities.AccountDetails;
import com.bkap.entities.ERole;
import com.bkap.services.CategoryService;
import com.bkap.services.ProductService;
import com.bkap.services.SlideImageService;

@Controller
public class HomeController {
	
	@Autowired
	SlideImageService slideImageService;
	@Autowired
	ProductService productService;
	@Autowired
	CategoryService categoryService;
	
	@GetMapping({"/","/trang-chu"})
	public String home(Model model) {
		model.addAttribute("slideshows", slideImageService.getAll());
		model.addAttribute("top5", productService.getTop5());
		return "home/index";
	}
	@GetMapping("/about-us")
	public String about(Model model) {
		return "home/about";
	}
	@GetMapping("/login")
	public String login(Model model) {
		return "home/login";
	}
	@GetMapping(value = "/success")
    public String success(){
        // điều hướng login
        try {
            AccountDetails account = (AccountDetails) SecurityContextHolder.getContext().getAuthentication()
                   .getPrincipal();
           if (account.getAuthorities().toString().contains(ERole.ROLE_ADMIN.name()))
               return "redirect:/backend";
       } catch (Exception e) {
           e.printStackTrace();
       }
       return "redirect:/";
    }
	@GetMapping("/contact-us")
	public String contact(Model model) {
		return "home/contact";
	}
	@GetMapping("/shop")
	public String shop(Model model) {
		model.addAttribute("categories", categoryService.getAll());
		model.addAttribute("products", productService.getAll());
		return "home/shop";
	}
	@GetMapping("/blog")
	public String blog(Model model) {
		return "home/blog";
	}
	@GetMapping("/product")
	public String product(Model model) {
		return "home/product";
	}
	@GetMapping("/slideshow")
	public String SlideShow(Model model) {
		model.addAttribute("slideshows", slideImageService.getAll());
		return "home/fragments/slide_show";
	}
}

Bước 10: Code các lớp HomeBackEndController ProductBackEndController trong gói admin

- Code lớp HomeBackEndController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.bkap.controller.admin;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeBackEndController {
	@GetMapping(value = { "/backend", "/backend/index" })
    public String index(Model model) {
        
            return "backend/index";
    }
    @GetMapping("/403")
    public String authorize(Model model) {
        return "home/403";
    }
}

- Code lớp ProductBackEndController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.bkap.controller.admin;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import ch.qos.logback.core.model.Model;

@Controller
@RequestMapping("/backend")
public class ProductBackEndController {
	@GetMapping("products")
	public String index(Model model) {
		return "backend/products/index";
	}
}

Bước 11: Chạy và kiểm tra kết quả

 

Source code tải tại đây (bao gồm cả file .sql chứa dữ liệu mẫu - lưu ý trước khi chạy insert data mẫu vào nhé)

Video demo (Đang quay...)

 

thay lời cảm ơn!

QUẢNG CÁO - TIẾP THỊ