Mớ Hỗn Độn Xác Thực Làm Khổ Cả Team
Ban đầu bạn chỉ có một ứng dụng. Người dùng đăng ký, đăng nhập, xong. Rồi service thứ hai ra đời — một admin dashboard riêng, một internal API, rồi có thể thêm cả customer portal. Mỗi cái lại có bảng user riêng, form đăng nhập riêng, luồng reset mật khẩu riêng.
Sáu tháng sau, hộp thư hỗ trợ ngập tràn “Em quên mật khẩu đăng nhập X rồi.” Dev thì copy-paste code auth qua hết service này đến service khác. Audit bảo mật phát hiện ba app đang lưu mật khẩu bằng MD5. Team này dùng JWT, team kia dùng session, team nữa thì… không expire token bao giờ cả.
Đây không phải vấn đề kỷ luật. Đây là vấn đề kiến trúc.
Nguyên Nhân Gốc Rễ: Logic Xác Thực Rải Rác Khắp Nơi
Khi mỗi service tự quản lý xác thực của mình, hậu quả là:
- Nhiều cơ sở dữ liệu user bị lệch nhau theo thời gian
- Không có nơi tập trung để áp dụng chính sách mật khẩu hay MFA
- Người dùng phải đăng nhập riêng vào từng ứng dụng
- Không có audit trail ghi lại ai truy cập gì, lúc nào
- Thu hồi quyền rất đau — vô hiệu hóa một tài khoản phải đụng vào từng hệ thống
Xác thực là hạ tầng. Bạn không thể cấp cho mỗi microservice một database server riêng. Cho mỗi service một auth stack riêng cũng là sai lầm tương tự, chỉ là khó phát hiện hơn cho đến khi có sự cố xảy ra.
Ba Lựa Chọn, Một Người Thắng Rõ Ràng
Hầu hết các team đều phải chọn giữa những hướng sau. Dưới đây là đánh đổi thực tế của từng cái:
Lựa chọn 1: Tự Xây SSO
Tự xây dịch vụ auth dùng chung cho phép bạn kiểm soát hoàn toàn. Nhưng cũng mất vài tháng. OAuth 2.0 và OpenID Connect có hàng chục edge case — token introspection, refresh flow, PKCE — và ai đó phải bảo trì thứ bạn tạo ra, mãi mãi. Hầu hết các team thử cách này đều ship được v1 hoạt động rồi âm thầm dừng cải thiện.
Lựa chọn 2: Auth-as-a-Service (Auth0, Okta, Cognito)
Các nhà cung cấp trên cloud xử lý tất cả sẵn có: MFA, đăng nhập mạng xã hội, SAML, SCIM provisioning. Nhưng đánh đổi là có thật. Auth0 có thể tốn hơn 1.000 đô/tháng với 10.000 MAU. Cognito có nhiều điểm thô ráp xung quanh custom token claims. Dữ liệu user của bạn nằm trên server của người khác. Với các ngành bị quản lý chặt hoặc team chú trọng privacy, chỉ điểm cuối thôi đã là lý do loại.
Lựa chọn 3: Tự Cài Keycloak
Keycloak là nền tảng identity mã nguồn mở của Red Hat. OAuth 2.0, OpenID Connect, SAML 2.0, đăng nhập mạng xã hội, MFA, user federation (LDAP/Active Directory), phân quyền chi tiết — tất cả đều có, không phí bản quyền. Bạn chạy trên hạ tầng của mình. Dữ liệu không đi đâu.
Với các team đang tự quản lý server, Keycloak trên Docker là con đường thực tế nhất. Bạn chỉ trả chi phí setup một lần.
Deploy Keycloak trên Docker
Hướng dẫn dưới đây giúp bạn có một instance Keycloak sẵn sàng cho production chạy qua Docker Compose, với PostgreSQL làm backend.
1. Tạo File Docker Compose
mkdir keycloak && cd keycloak
nano docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16
container_name: keycloak_db
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- keycloak_net
keycloak:
image: quay.io/keycloak/keycloak:24.0
container_name: keycloak
restart: unless-stopped
command: start
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${DB_PASSWORD}
KC_HOSTNAME: auth.yourdomain.com
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "true"
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
ports:
- "8080:8080"
depends_on:
- postgres
networks:
- keycloak_net
volumes:
postgres_data:
networks:
keycloak_net:
Một lưu ý về KC_PROXY_HEADERS: xforwarded: tùy chọn này thay thế KC_PROXY: edge đã bị deprecated từ Keycloak 20 trở về trước. Kết hợp với KC_HTTP_ENABLED: "true", nó báo cho Keycloak tin tưởng các header X-Forwarded-* từ reverse proxy — để Keycloak biết IP thực và giao thức thực của client.
2. Thiết Lập Biến Môi Trường
Đừng bao giờ hardcode mật khẩu trong file Compose. Tạo file .env cùng thư mục:
nano .env
DB_PASSWORD=mật_khẩu_db_mạnh_của_bạn
ADMIN_PASSWORD=mật_khẩu_admin_mạnh_của_bạn
Để tạo những mật khẩu đó, mình dùng công cụ tại toolcraft.app/vi/tools/security/password-generator — chạy hoàn toàn trên trình duyệt, không gọi lên server. Điều này quan trọng khi bạn đang tạo thông tin xác thực cho hạ tầng identity.
3. Khởi Động Keycloak
docker compose up -d
docker compose logs -f keycloak
Chú ý dòng: Keycloak 24.0 on JVM (powered by Quarkus) started. Lần đầu khởi động mất 30–60 giây — Keycloak chạy database migration khi startup.
4. Tạo Realm Đầu Tiên
Một realm là một namespace độc lập. User, client và role trong realm này không thấy được realm kia. Hãy coi nó như ranh giới tenant — hoặc một container cấp tổ chức.
- Mở
http://localhost:8080(hoặc domain của bạn) - Đăng nhập bằng thông tin admin
- Rê chuột vào master ở góc trên bên trái → click Create realm
- Đặt tên ví dụ
myapp→ click Create
Giữ các ứng dụng thực tế ra khỏi realm master. Master chỉ dành cho quản trị Keycloak.
5. Đăng Ký Ứng Dụng Là Một Client
Bất kỳ ứng dụng nào ủy thác xác thực cho Keycloak đều được gọi là client.
- Trong realm của bạn, vào Clients → Create client
- Chọn Client type là
OpenID Connect - Đặt Client ID theo tên ứng dụng (ví dụ:
webapp) - Bật Client authentication nếu đây là backend service (confidential client)
- Điền Valid redirect URIs: ví dụ
https://yourapp.com/* - Lưu → mở tab Credentials và copy client secret
6. Tạo User Thử Nghiệm
# Hoặc dùng giao diện admin: Users → Add user
docker exec -it keycloak /opt/keycloak/bin/kcadm.sh \
create users \
-r myapp \
-s username=testuser \
-s enabled=true \
--server http://localhost:8080 \
--realm master \
--user admin \
--password ${ADMIN_PASSWORD}
Sau đó gán mật khẩu: Users → testuser → Credentials → Set password.
7. Kiểm Tra Luồng Xác Thực
Keycloak cung cấp OIDC discovery document chuẩn — mọi thư viện tương thích đều đọc được tự động:
curl https://auth.yourdomain.com/realms/myapp/.well-known/openid-configuration
Để lấy token trực tiếp (hữu ích khi test API):
curl -X POST \
https://auth.yourdomain.com/realms/myapp/protocol/openid-connect/token \
-d 'grant_type=password' \
-d 'client_id=webapp' \
-d 'client_secret=YOUR_CLIENT_SECRET' \
-d 'username=testuser' \
-d 'password=testpassword'
Kết quả trả về gồm access_token (một JWT đã ký), refresh_token, và thời gian hết hạn. Các backend service xác thực access token dựa trên public key của Keycloak — không cần tra cứu database, không cần round-trip đến Keycloak mỗi request.
Đặt Nginx Phía Trước
File Compose đã set KC_PROXY_HEADERS: xforwarded, báo Keycloak rằng nó đứng sau reverse proxy. Config Nginx của bạn cần có chứng chỉ SSL/TLS hợp lệ:
server {
listen 443 ssl;
server_name auth.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Bật MFA
Đây là lúc tập trung hóa thực sự phát huy giá trị. Bật MFA cho toàn bộ ứng dụng trong một realm chỉ mất khoảng 30 giây:
- Vào realm của bạn → Authentication → Policies
- Trong OTP Policy, cấu hình thuật toán TOTP và cửa sổ thời gian
- Trong Flows, chỉnh flow Browser để yêu cầu OTP bắt buộc
Xong. Mọi ứng dụng kết nối với realm này đều bắt buộc MFA khi đăng nhập. Không cần thay đổi một dòng code nào trong ứng dụng.
Thay Đổi Cụ Thể Trong Vận Hành Hàng Ngày
Khi hệ thống đã chạy, đây là sự khác biệt thực tế:
- Đăng nhập một lần: Đăng nhập một lần, truy cập mọi ứng dụng trong realm — không cần xác thực lại giữa các service
- Thu hồi quyền tức thì: Vô hiệu hóa tài khoản trong Keycloak và người dùng đó mất quyền truy cập mọi ứng dụng ngay lập tức, không phải chờ đợi
- Chính sách bảo mật nhất quán: Độ phức tạp mật khẩu, yêu cầu MFA, thời gian hết hạn session — cấu hình một lần, áp dụng khắp nơi
- Audit trail: Mọi đăng nhập, đăng xuất và trao đổi token đều được ghi lại ở một chỗ
- Đăng nhập mạng xã hội: Thêm đăng nhập Google hoặc GitHub vào bất kỳ ứng dụng nào mà không cần chạm vào code ứng dụng
Việc setup chỉ mất tối đa một buổi chiều. Và nó hoàn vốn rất nhanh — ít ticket hỗ trợ hơn, không còn code auth trùng lặp giữa các service, và chính sách bảo mật bạn thực sự có thể audit được. Nếu bạn đang chạy hơn hai ứng dụng dùng chung một tập người dùng, IAM tập trung không còn là tùy chọn nữa. Đó đơn giản là thứ hạ tầng nên có.
