Deploy Keycloak trên Docker: Xây dựng hệ thống IAM và SSO tập trung cho ứng dụng của bạn

Security tutorial - IT technology blog
Security tutorial - IT technology blog

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.

  1. Mở http://localhost:8080 (hoặc domain của bạn)
  2. Đăng nhập bằng thông tin admin
  3. Rê chuột vào master ở góc trên bên trái → click Create realm
  4. Đặ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.

  1. Trong realm của bạn, vào ClientsCreate client
  2. Chọn Client typeOpenID Connect
  3. Đặt Client ID theo tên ứng dụng (ví dụ: webapp)
  4. Bật Client authentication nếu đây là backend service (confidential client)
  5. Điền Valid redirect URIs: ví dụ https://yourapp.com/*
  6. 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:

  1. Vào realm của bạn → AuthenticationPolicies
  2. Trong OTP Policy, cấu hình thuật toán TOTP và cửa sổ thời gian
  3. 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ó.

Share: