Giải thích cơ chế hoạt động của tính năng đặt mật khẩu cho bài viết
Back to ezyarticleTính năng đặt mật khẩu cho bài viết được triển khai theo mô hình “bảo vệ tại thời điểm đọc”. Bài viết vẫn tồn tại trong hệ thống như bình thường, nhưng trước khi trả nội dung chi tiết, hệ thống kiểm tra người đọc có quyền vượt qua lớp mật khẩu hay chưa.
Có ba trạng thái chính:
- Bài viết không có mật khẩu: cho phép đọc bình thường.
- Bài viết có mật khẩu và request có mật khẩu hợp lệ: cho phép đọc.
- Bài viết có mật khẩu nhưng request không có hoặc sai mật khẩu: trả về màn hình nhập mật khẩu, hoặc lỗi
password: invalidnếu là API.
Lưu mật khẩu
Khi admin nhập mật khẩu và lưu bài viết, hệ thống không lưu mật khẩu thô trực tiếp. Mật khẩu được hash trước khi ghi vào bản ghi bài viết.
Nếu ô mật khẩu bị bỏ trống, mật khẩu của bài viết được xóa. Từ lúc đó bài viết không còn yêu cầu xác thực mật khẩu nữa.
Có một chi tiết đáng chú ý ở màn hình admin: sau khi đồng bộ/lấy dữ liệu từ server, giá trị mật khẩu trả về có thể là hash. Vì vậy UI admin lưu mật khẩu người dùng đang nhập trong
localStorage để có thể tạo link kèm mật khẩu thô, thay vì lấy hash từ server để đưa vào URL.Tạo link kèm mật khẩu
Trong màn hình chỉnh sửa, admin có hai loại link:
- Link web: mở bài viết ở website public.
- Link admin: mở bài viết ở khu vực quản trị.
Nếu trình soạn thảo đang có mật khẩu thô trong ô mật khẩu hoặc trong
localStorage, link copy ra sẽ được thêm query parameter dạng:
?ezyarticle_post_{postId}_password={encodedPassword}
Ví dụ khái niệm:
https://example.com/my-post?ezyarticle_post_123_password=secret
Trong thực tế, password được URL encode để tránh lỗi với ký tự đặc biệt.
Nếu không có mật khẩu đang được lưu ở phía trình duyệt admin, link copy ra sẽ là link sạch, không kèm query parameter. Khi mở link này, người nhận phải nhập mật khẩu thủ công.
Kiểm tra quyền đọc bài viết
Trước khi trả chi tiết bài viết, cả web runtime và admin runtime đều gọi bước validate mật khẩu.
Luồng kiểm tra có thứ tự như sau:
- Nếu người đang đăng nhập là tác giả admin của bài viết, cho qua.
- Nếu người đang đăng nhập là tác giả user của bài viết, cho qua.
- Nếu bài viết không có mật khẩu, cho qua.
- Nếu request có mật khẩu hợp lệ, cho qua.
- Nếu không thỏa các điều kiện trên, ném lỗi mật khẩu không hợp lệ.
Mật khẩu trong request có thể đến từ query parameter hoặc cookie. Tên chính là:
ezyarticle_post_{postId}_password
Ngoài ra hệ thống còn có fallback theo tên cookie/chìa khóa chung cho mật khẩu bài viết.
Luồng mở bài viết
flowchart TD
A[Người dùng mở link bài viết] --> B[Controller lấy bài viết theo slug hoặc id]
B --> C{Bài viết tồn tại?}
C -- Không --> D[Trả 404]
C -- Có --> E[Validate mật khẩu]
E --> F{Là tác giả?}
F -- Có --> G[Trả nội dung bài viết]
F -- Không --> H{Bài viết có mật khẩu?}
H -- Không --> G
H -- Có --> I{Request có mật khẩu đúng?}
I -- Có --> G
I -- Không --> J{Là API request?}
J -- Có --> K[400 password: invalid]
J -- Không --> L[Hiển thị form nhập mật khẩu]
Luồng nhập mật khẩu
Khi người đọc mở link không kèm mật khẩu, hệ thống render màn hình nhập mật khẩu. Form này gọi API xác thực:
- Web:
POST /api/v1/posts/{id}/authenticate - Admin:
POST /ezyarticle/api/v1/posts/{id}/authenticate
Request body chứa mật khẩu người dùng nhập.
Nếu mật khẩu đúng, server set cookie theo bài viết:
ezyarticle_post_{postId}_password
Cookie được set với:
-
path=/ -
HttpOnly=true -
maxAgetheo cấu hình thời hạn mật khẩu bài viết
Sau đó trình duyệt quay lại
callbackUri, tức URL ban đầu người dùng muốn mở.
sequenceDiagram
participant U as User
participant B as Browser
participant S as Server
U->>B: Mở link không kèm mật khẩu
B->>S: GET /post-slug
S->>S: Kiểm tra password thất bại
S-->>B: Render form nhập mật khẩu
U->>B: Nhập mật khẩu
B->>S: POST /api/v1/posts/{id}/authenticate
S->>S: So khớp mật khẩu với hash đã lưu
alt Mật khẩu đúng
S-->>B: 204 + Set-Cookie
B->>S: GET callbackUri
S-->>B: Trả nội dung bài viết
else Mật khẩu sai
S-->>B: 400 password: invalid
B-->>U: Hiển thị lỗi
end
Link kèm mật khẩu khác gì nhập mật khẩu?
Link kèm mật khẩu chỉ đưa mật khẩu vào request hiện tại qua query parameter. Nó giúp request đó vượt qua bước validate nếu mật khẩu đúng.
Còn khi người dùng nhập mật khẩu qua form, server sẽ set cookie. Cookie giúp các request sau trên cùng trình duyệt tiếp tục được xác thực mà không cần nhập lại ngay.
Vì vậy, về mặt bảo mật và SEO, link không kèm mật khẩu vẫn nên là mặc định. Link kèm mật khẩu tiện hơn, nhưng password nằm trong URL nên có thể bị lộ qua lịch sử trình duyệt, log, công cụ phân tích, hoặc bị crawler ghi nhận nếu link xuất hiện ở nơi công khai.
Tác động tới hiển thị và index
Khi bài viết có mật khẩu, link bài viết trong hệ thống được đánh trạng thái không hiển thị trong cơ chế link nội bộ. Điều này giúp hạn chế việc bài viết được đưa vào các danh sách link public như một nội dung bình thường.
Tuy nhiên, nếu ai đó chia sẻ công khai link có kèm mật khẩu, crawler vẫn có thể nhìn thấy URL đó. Vì vậy khuyến nghị vận hành là: chia sẻ link sạch, gửi mật khẩu qua kênh riêng.