2.4 — 2. Client - Server Model
Mô hình client–server có hai luật chi phối gần như mọi quyết định thiết kế backend. Thứ nhất: client luôn là bên bắt chuyện — máy chủ không tự gọi tới trình duyệt được, và đó là lý do tồn tại của polling, WebSocket và SignalR. Thứ hai, và quan trọng hơn: HTTP không nhớ gì giữa hai request, nên máy chủ không mặc nhiên biết request này đến từ ai — đó là lý do sinh ra cookie, session và JWT. Cộng thêm một nguyên tắc an toàn không có ngoại lệ: mọi byte đến từ client đều là dữ liệu do người lạ gửi, kể cả khi chính bạn viết ra cái client đó.
Mục tiêu bài học
Sau bài này bạn có thể:
- Giải thích vì sao máy chủ không chủ động g ửi dữ liệu tới client được, và ba cách vượt qua giới hạn đó.
- Nêu ý nghĩa của "HTTP là stateless" và hệ quả của nó lên thiết kế đăng nhập.
- Chỉ ra vì sao kiểm tra ở phía client không bao giờ là bảo mật.
- Thiết kế API phục vụ nhiều loại client mà không phá vỡ client cũ.
Nội dung bài học
2.4.1 — Ai bắt chuyện trước
Trong HTTP thuần, máy chủ không bao giờ nói trước. Nó chỉ trả lời. Muốn đẩy dữ liệu xuống client theo thời gian thực thì phải dùng một trong ba cách:
| Cách | Nguyên lý | Dùng khi |
|---|---|---|
| Polling | Client hỏi lại theo chu kỳ | Đơn giản, dữ liệu đổi chậm |
| Server-Sent Events | Một kết nối mở, server đẩy một chiều | Thông báo, luồng sự kiện |
| WebSocket | Kết nối hai chiều, giữ mở | Chat, cộng tác thời gian thực |
Trong .NET, cả ba đều gói lại trong SignalR, và nó tự chọn cách tốt nhất mà môi trường cho phép — chủ đề của Module 11.
2.4.2 — HTTP không nhớ gì
Đây là đặc tính bị bỏ qua nhiều nhất, và nó giải thích rất nhiều thứ.
Mỗi request HTTP là một tờ giấy trắng. Máy chủ không biết request này đến từ ai, trừ khi chính request đó mang theo bằng chứng.
Vì thế mới có:
# Request 1 — đăng nhập
POST /api/auth/login
{ "email": "a@company.com", "password": "..." }
→ 200 OK
{ "token": "eyJhbGciOi..." }
# Request 2 — hoàn toàn độc lập với request 1
GET /api/customers
Authorization: Bearer eyJhbGciOi...
↑ phải tự mang theo bằng chứng, server không tự nhớ
Không có dòng Authorization đó, máy chủ coi bạn là người lạ — kể cả khi bạn vừa đăng nhập một giây trước.
Đánh đổi của tính stateless: máy chủ không cần nhớ gì, nên đặt bao nhiêu máy chủ cũng được và request đi vào máy nào cũng xử lý được như nhau. Đó là nền tảng của việc mở rộng ngang. Cái giá là mỗi request nặng hơn vì phải mang theo thông tin xác thực.
2.4.3 — Client không đáng tin. Không có ngoại lệ.
Đây là điều quan trọng nhất trong cả bài.
// Phía client — JavaScript trong trình duyệt
if (user.role === 'admin') {
showDeleteButton(); // chỉ là TRẢI NGHIỆM NGƯỜI DÙNG
}
Đoạn trên không phải bảo mật. Nó chỉ là ẩn một cái nút. Người dùng có thể mở DevTools, sửa biến, hoặc bỏ qua giao diện hoàn toàn:
curl -X DELETE https://api.company.com/customers/42 \
-H "Authorization: Bearer <token của tài khoản thường>"
Nếu máy chủ không tự kiểm tra quyền, bản ghi đó bị xoá.
Danh sách những thứ không bao giờ được tin, dù client là do chính bạn viết:
- Giá tiền gửi lên từ giỏ hàng — luôn tính lại từ database.
userIdtrong body request — luôn lấy từ token đã xác thực.- Kết quả kiểm tra form phía client — luôn kiểm tra lại ở máy chủ.
- Việc một nút bị ẩn — luôn kiểm tra quyền ở endpoint.
Lỗ hổng sinh ra từ việc tin id mà client gửi lên có tên riêng: IDOR. Bài Đổi id trên URL ra dữ liệu người khác mổ xẻ nó và chỉ cách chặn ở một tầng duy nhất. Đây cũng là hạng mục đứng đầu OWASP Top 10.