Tổng Quan Về Session
Trong quá trình phát triển ứng dụng web, việc duy trì trạng thái người dùng giữa các lần yêu cầu là một nhu cầu cơ bản. Công nghệ Session đóng vai trò trung tâm trong việc này. Khác với Cookie – vốn lưu trữ thông tin phía trình duyệt của khách hàng – Session hoạt động hoàn toàn trên máy chủ. Điều này giúp dữ liệu nhạy cảm được bảo mật tốt hơn vì không thể truy cập trực tiếp từ client.
Hệ thống Session hoạt động dựa trên sự cộng tác với Cookie. Khi một yêu cầu đầu tiên đến server, một ID duy nhất sẽ được sinh ra để định danh phiên làm việc đó.
1. Quy Trình Hoạt Động Của Session
Lưu đồ xử lý tiêu chuẩn diễn ra như sau:
- Một khách hàng gửi yêu cầu HTTP tới Server. Do Session cần Cookie để nhận diện, Server sẽ tạo ra một
SessionIDđộc nhất trong bộ nhớ đệm. - Server truyền
SessionIDnày trở lại cho Client dưới dạng giá trị thuộc tính trong Cookie (Set-Cookie Header). - Khi Client gửi yêu cầu tiếp theo, Cookie sẽ tự động đính kèm
SessionIDvào header. - Server nhận diện
SessionIDnày để tìm kiếm dữ liệu tương ứng trong bộ nhớ, xác định đây là cùng một phiên làm việc.
2. Đặc Tính Kỹ Thuật
- Dung lượng lưu trữ: Session không giới hạn kích thước dữ liệu so với Cookie (thường bị giới hạn khoảng 4KB).
- Type dữ liệu: Có thể lưu trữ bất kỳ đối tượng Java nào (Object), không chỉ chuỗi.
- Vị trí lưu: Hoàn toàn nằm trong RAM của máy chủ (Tomcat/Jetty).
3. So Sánh Với Cookie
| Đặc điểm | Cookie | Session |
|---|---|---|
| Vị trí lưu trữ | Clientside (Trình duyệt) | Serverside (Máy chủ) |
| Giới hạn kích thước | Có (khoảng 4KB mỗi cookie) | Gần như vô hạn (tùy RAM) |
| An toàn | Thấp (có thể bị can thiệp) | Cao (người dùng không thấy nội dung) |
4. Các Phương Thức Thường Dùng
Class HttpSession cung cấp các hàm quan trọng để quản lý dữ liệu:
// Lấy hoặc tạo mới Session object
HttpSession session = request.getSession();
// Lưu dữ liệu vào session
session.setAttribute("keyData", objectValue);
// Đọc dữ liệu đã lưu
Object value = session.getAttribute("keyData");
// Xóa dữ liệu cụ thể
session.removeAttribute("keyData");
// Hủy bỏ toàn bộ session ngay lập tức
session.invalidate();
5. Vòng Đời Và Xử Lý Sự Cố
Session có thể bị hủy trong các trường hợp sau:
- Người dùng tắt trình duyệt (nếu Cookie chưa set thời gian tồn tại lâu - session cookie).
- Thời gian chờ vượt quá cấu hình mặc định (thường là 30 phút).
- Dev gọi hàm
invalidate().
Về mặt kỹ thuật, nếu Server bị sập đột ngột, dữ liệu Session sẽ mất đi trừ khi có cơ chế Persist (lưu vào ổ đĩa). Tomcat có cơ chế Serialization/Deserialization để lưu Session vào disk khi shutdown (Deadlock/Persistence) và khôi phục khi khởi động lại, đảm bảo dữ liệu không thất thoát ngay lập tức.
6. Ví Dụ Thực Tiễn: Hệ Thống Đăng Nhập Với Mã Xác Thực
Dưới đây là minh họa cách tích hợp Session vào Spring Boot kết hợp JSP để xây dựng chức năng đăng nhập với mã bảo vệ (Captcha).
Bước 1: Controller Tạo Hình Xác Thực
Phần tử này chịu trách nhiệm vẽ mã ngẫu nhiên và lưu nó vào Session.
@Controller
@RequestMapping("/auth")
public class VerificationController {
@GetMapping("/create-verify-code")
public void createCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
int width = 150;
int height = 60;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
// Vẽ nền xanh lá nhạt
g.setColor(new Color(200, 255, 200));
g.fillRect(0, 0, width, height);
String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
Random rnd = new Random();
StringBuilder sb = new StringBuilder();
// Thêm 4 ký tự ngẫu nhiên
for (int i = 0; i < 4; i++) {
char c = chars.charAt(rnd.nextInt(chars.length()));
sb.append(c);
// Gác màu chữ ngẫu nhiên
g.setColor(new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
g.setFont(new Font("Arial", Font.BOLD, 35));
g.drawString(String.valueOf(c), 20 + i * 35, 40);
}
String codeSession = sb.toString();
req.getSession().setAttribute("captcha_code", codeSession.toUpperCase());
// Vẽ đường nhiễu
for(int i=0; i<20; i++){
g.setColor(Color.YELLOW);
g.drawLine(rnd.nextInt(width), rnd.nextInt(height), rnd.nextInt(width), rnd.nextInt(height));
}
ImageIO.write(img, "png", resp.getOutputStream());
g.dispose();
}
}
Bước 2: Trang Giao Diện Đăng Nhập (login.jsp)
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="javax.servlet.http.HttpServletRequest"%>
<%
String basePath = request.getContextPath();
%>
<!DOCTYPE html>
<html>
<head>
<title>Đăng Nhập System</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
.form-container { width: 300px; margin: 100px auto; padding: 20px; background: white; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
input[type="text"], input[type="password"] { width: 80%; padding: 8px; margin: 5px 0; }
img#captureImg { cursor: pointer; border: 1px solid #ccc; vertical-align: middle; }
</style>
<script>
window.onload = function () {
document.getElementById('captureImg').onclick = function () {
this.src = '/auth/create-verify-code?t=' + new Date().getTime();
};
}
</script>
</head>
<body>
<div class="form-container">
<h2 align="center">Vào Hệ Thống</h2>
<form action="/auth/process-login" method="post">
<p>Tên đăng nhập:<br><input type="text" name="inputUser" /></p>
<p>Mật khẩu:<br><input type="password" name="inputPass" /></p>
<p>Mã xác nhận:<br>
<input type="text" name="inputVerify" />
<img id="captureImg" src="/auth/create-verify-code" />
</p>
<p align="center">
<button type="submit">Gửi Yêu Cầu</button>
</p>
</br>
<font color="red">${error_msg}</font>
</form>
</div>
</body>
</html>
Bước 3: Trang Hiển Thị Sau Khi Thành Công
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="javax.servlet.http.HttpSession"%>
<body>
<h1 align="center">Chúc Mừng Bạn Đã Đăng Nhập!</b></font></p>
<a href="/logout">Thoát phiên làm việc</a>
</body>
Bước 4: Xử Lý Logic Xác Thực
Controller chính thức kiểm tra thông tin đầu vào so với dữ liệu đã lưu.
@Controller
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/process-login")
public String verifyUser(HttpServletRequest req, Model model) {
// Thiết lập mã hóa
req.setCharacterEncoding("UTF-8");
String username = req.getParameter("inputUser");
String password = req.getParameter("inputPass");
String inputVerify = req.getParameter("inputVerify");
HttpSession session = req.getSession(false);
String storedCode = null;
if (session != null) {
storedCode = (String) session.getAttribute("captcha_code");
}
// Kiểm tra xem Session tồn tại và mã code khớp nhau
if (storedCode == null || !storedCode.equalsIgnoreCase(inputVerify)) {
model.addAttribute("error_msg", "Mã xác thực không đúng hoặc phiên đã hết hạn.");
return "login";
}
// Giả lập so sánh tài khoản (Thông thường query DB)
if ("admin".equals(username) && "12345".equals(password)) {
// Lưu tên người dùng vào session
session.setAttribute("name_user", username);
// Xóa code sau khi login thành công
session.removeAttribute("captcha_code");
return "redirect:/dashboard-view";
} else {
model.addAttribute("error_msg", "Tài khoản hoặc mật khẩu sai.");
return "login";
}
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/index.jsp";
}
}
Qua đoạn code trên, chúng ta thấy rõ cách Session lưu trữ trạng thái tạm thời của phiên đăng nhập (mã captcha, tên người dùng) mà không expose dữ liệu lên client.