🔐 Hướng Dẫn Xây Dựng JWT, Refresh Token và Blacklist Với Redis Trong Spring Boot
⚠️ Lưu ý: Đây là hướng tiếp cận DIY (Do-It-Yourself) phục vụ mục đích học tập, tìm hiểu, KHÔNG dùng cho thực tế đâu. Trong môi trường thực tế, bạn nên sử dụng Authorization Server chuyên dụng như Keycloak, Auth0, hoặc các nền tảng IDaaS.
Ứng Dụng Này Làm Gì?
- Đăng ký, đăng nhập người dùng
- Sinh ra
accessToken
vàrefreshToken
- Cho phép lấy access token mới qua refresh token
- Cho phép logout và thực hiện blacklist access token
- Bảo vệ các API bằng
JWT
- Sử dụng
Redis
để lưu blacklist token với TTL tương ứng
⚙️ Cơ Chế Blacklist Hoạt Động Thế Nào?
-
Khi logout, ta sẽ:
- Xoá
refreshToken
khỏi database - Cho
accessToken
vào Redis blacklist kèm TTL (time-to-live)
- Xoá
-
Nếu access token bị nằm trong danh sách blacklist → từ chối truy cập
-
Sau khi hết hạn TTL, Redis tự động xoá key token khỏi blacklist
-
Token hết hạn theo thời gian vẫn bị từ chối như bình thường (không cần blacklist)
DB Shema
- refresh_tokens table: lưu refresh token cho từng user (dễ revoke thủ công hoặc theo session)
- Redis: lưu access token bị revoke (dạng
String
key-value, TTL = thời gian còn lại đến hết hạn)
🔑 Các API Chính
Auth API
Endpoint | Method | Chức năng |
---|---|---|
/api/auth/register |
POST | Đăng ký tài khoản mới |
/api/auth/login |
POST | Đăng nhập |
/api/auth/refresh-token |
POST | Lấy access token mới |
/api/auth/logout |
POST | Logout, thu hồi token |
Secure API (Yêu cầu JWT)
Header: Authorization: Bearer <access_token>
Endpoint | Method | Chức năng |
---|---|---|
/api/user |
GET | Truy cập user content |
/api/admin |
GET | Truy cập admin content |
/api/demo |
GET | API test lỗi 404 |
💻 Thử Nghiệm Với Postman
- Clone repo tại: GitHub - spring-security6/jwt-rf-blacklist
- Import file
api.json
trong repo vào Postman để thử từng API
Lệnh Clone:
git clone --filter=blob:none --no-checkout https://github.com/thnhan1/spring-security6.git
cd spring-security6
git sparse-checkout init --cone
git sparse-checkout set jwt-rf-blacklist
🔄 Ví Dụ Response Đăng Nhập
{ "accessToken": "eyJhbGciOiJIUzI1NiJ9...", "tokenType": "Bearer", "expiresAt": "2025-07-09T02:10:40.394009100Z", "refreshToken": "d2dc2e5c-40b5-4754-8df7-ce386206acb1"
}
❓ Câu Hỏi Thường Gặp
1. Sau khi refresh token, access token cũ còn dùng được không?
Có. Chỉ khi nào logout thì token mới bị revoke thông qua blacklist. Ngoài ra, access token sẽ có hiệu lực cho đến khi hết hạn.
2. Nếu user đổi role thì token cũ vẫn còn giá trị?
Đúng. JWT không truy vấn database mỗi lần request. Do đó:
- Nếu cần cập nhật quyền, FE nên gọi
/refresh-token
để lấy token mới chứa role cập nhật - Nếu cần mạnh tay hơn, backend có thể revoke các token hiện tại hoặc dùng phiên bản token (
tokenVersion
) để vô hiệu hóa toàn bộ
3. Refresh token lưu ở đâu?
Trong hướng dẫn này, refreshToken
được lưu trong database (có thể là PostgreSQL, MySQL...). Bạn cũng có thể lưu vào Redis hoặc kết hợp với device/session ID
.
4. Blacklist token trong Redis lưu thế nào?
- Key:
blacklisted:<access_token>
- Value:
"true"
hoặc rỗng - TTL: tính từ thời điểm logout đến lúc token hết hạn
Chú ý.
🔒 Trong thực tế, để đảm bảo bảo mật và scalability, bạn nên dùng các công cụ như:
- Supabase: Free và phù hợp với ứng dụng Node.js
- Firebase Auth: Quá dễ dùng, nhưng phụ thuộc vào nó, phủ custom role.
- Keycloak mạnh mẽ, hơi phức tạp với ứng dụng nhỏ.
- Auth0 Good job. Docs ok.
- OAuth2 Authorization Server của Spring
Q n A
Đặt câu hỏi nhé