Keycloak: Giải pháp IAM toàn diện cho ứng dụng hiện đại

Keycloak là một giải pháp quản lý danh tính và truy cập (IAM – Identity and Access Management) mã nguồn mở, giúp bạn triển khai đăng nhập, phân quyền và bảo mật cho ứng dụng mà không cần tự xây từ đầu.

Bảo mật luôn là yếu tố cốt lõi trong quá trình phát triển ứng dụng, đặc biệt khi bạn phải xử lý việc xác thực và phân quyền cho nhiều người dùng khác nhau.

Dù các developer hoàn toàn có thể tự xây dựng hệ thống quản lý danh tính từ con số 0, nhưng việc này tốn rất nhiều thời gian, công sức và dễ phát sinh lỗi – nhất là khi phải hỗ trợ các giao thức tiêu chuẩn như OAuth2 hay OpenID Connect.

Keycloak xuất hiện như một giải pháp giúp đơn giản hóa toàn bộ quá trình này. Nó không chỉ giảm tải việc triển khai xác thực mà còn mang đến sự đồng nhất, bảo mật cao và khả năng tùy biến mạnh mẽ. Bạn có thể tích hợp đăng nhập bằng Google, Facebook hoặc thiết lập SSO chỉ trong vài bước cấu hình mà không cần viết lại cả hệ thống.

1. What? Keycloak là gì?

Keycloak là một nền tảng mã nguồn mở mạnh mẽ, cung cấp giải pháp đăng nhập một lần (SSO) cùng hệ thống quản lý danh tính và quyền truy cập cho các ứng dụng và dịch vụ.

Nền tảng này được thiết kế nhằm đơn giản hóa toàn bộ bài toán bảo mật, giúp developer dễ dàng tích hợp cơ chế đăng nhập, phân quyền và xác thực vào ứng dụng mà không phải xây dựng mọi thứ từ đầu. Tất cả đều được Keycloak cung cấp sẵn dưới dạng các tính năng linh hoạt, dễ tùy chỉnh để phù hợp với nhu cầu của từng doanh nghiệp hay tổ chức.


Keycloak cho phép bạn tùy chỉnh giao diện người dùng cho các trang đăng nhập, đăng ký, quản lý tài khoản hay trang quản trị. Bên cạnh đó, nó còn hỗ trợ tích hợp với các hệ thống quản lý danh tính sẵn có như LDAP hoặc Active Directory, và cho phép xác thực thông qua các nhà cung cấp bên thứ ba như Google hay Facebook.

Tóm lại: đối với developer, Keycloak giống như một “bộ công cụ all-in-one” để giải quyết toàn bộ vấn đề liên quan đến đăng nhập và phân quyền. Thay vì tự xây các tính năng như login, role-based access hay kết nối với các nền tảng bên ngoài, bạn chỉ cần cấu hình và sử dụng những gì Keycloak đã cung cấp.

Luồng hoạt động:
+ Spring Boot kiểm tra Token và cho phép truy cập nếu hợp lệ.
+ User đăng nhập vào Keycloak.
+ Keycloak trả về Token.
+ User gửi request kèm Token đến Spring Boot.

2. Why? Tại sao lại là Keycloak?

Thay vì tự xây dựng module đăng nhập (Login) trong Spring Boot, việc sử dụng Keycloak mang lại các lợi ích:

a. Quản lý tập trung: Admin có thể khóa user, đổi password, cấp quyền ngay trên giao diện Keycloak mà không cần sửa code hay database của ứng dụng.

b. Bảo mật chuyên sâu (Delegated Authentication): Việc xử lý mật khẩu, mã hóa, 2FA (xác thực 2 bước) được Keycloak đảm nhận. Spring Boot không cần lo về lộ mật khẩu.

c. Single Sign-On (SSO): Nếu bạn có 10 ứng dụng (Microservices), user chỉ cần đăng nhập 1 lần tại Keycloak là vào được cả 10 ứng dụng.

d. Chuẩn hóa (Standardization): Sử dụng giao thức OAuth2 và OpenID Connect chuẩn quốc tế. Dễ dàng tích hợp với Frontend (React, Angular, Mobile App).

3. Các thành phần chính trong Keycloak

3.1. Realm (Không gian quản lý)

Định nghĩa: Realm là một không gian quản lý định danh hoàn toàn độc lập. Dữ liệu (user, role, client) của Realm A hoàn toàn tách biệt với Realm B.

Master Realm: Khi cài đặt xong, Keycloak có sẵn realm tên là Master. Đây là realm dùng để quản trị hệ thống Keycloak. Không nên dùng realm này để quản lý user cho ứng dụng của bạn.

Ví dụ: Bạn có thể tạo Realm Users cho người dùng cuối và Realm Admins cho site quản trị.

3.2. Client (Ứng dụng khách)

Định nghĩa: Client là các ứng dụng hoặc dịch vụ muốn sử dụng Keycloak để xác thực. Client đại diện cho các ứng dụng hoặc dịch vụ được bảo mật bởi Keycloak. Mỗi client có cấu hình riêng, bao gồm giao thức (OAuth2, OpenID Connect), URL callback, và thông tin bảo mật như client secret.

3.3. User (Người dùng)

Định nghĩa: Thực thể có thể đăng nhập vào hệ thống.

Thuộc tính: User có Username, Password, Email, và các thuộc tính tùy chỉnh (Attributes) như số điện thoại, mã nhân viên, phòng ban…

Keycloak hỗ trợ các phương thức xác thực đa dạng:
– Đăng nhập bằng email/mật khẩu.
– Đăng nhập qua các nhà cung cấp bên thứ ba (Google, Facebook).
– Xác thực hai yếu tố (2FA).

3.4. Role (Vai trò)

Role dùng để phân quyền (Authorization). Có 2 loại Role trong Keycloak:
Realm Role: Role toàn cục. Ví dụ: Global_Admin, User. Có hiệu lực trên toàn bộ Realm.
Client Role: Role gắn liền với một Client cụ thể. Ví dụ: Client Inventory-App có role stock-keeper. Role này chỉ có ý nghĩa với app kho, không có ý nghĩa với app nhân sự.

3.5. Group (Nhóm)

Định nghĩa: Dùng để gom nhóm các User lại.

Đặc điểm: Group có thể lồng nhau (Parent – Child). Ví dụ: IT Department -> Dev Team.

Tác dụng: Bạn có thể gán Role cho Group, tất cả User trong Group đó sẽ tự động kế thừa Role. Giúp quản lý quyền hạn dễ dàng hơn gán lẻ tẻ.

4. How? Làm thế nào để tích hợp Keycloak vào 1 ứng dụng Spring Boot chỉ trong vài bước đơn giản.

Phần A: Cấu hình Keycloak (Docker)
Bước 1: Khởi chạy Keycloak Mở terminal và chạy lệnh Docker (bản developer):

ShellScript
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:24.0.1 start-dev

Bước 2: Thiết lập Realm và Client

  1. Truy cập http://localhost:8080 -> Vào Administration Console (Login: admin/admin).
  2. Tạo Realm: Bấm vào dropdown menu góc trái -> Create Realm -> Tên: springboot-demo.
  3. Tạo Client:
    – Vào menu Clients -> Create client.
    – Client ID: my-backend-api.
    – Capability config: Bật Client authentication (để có Client Secret) và Authorization.
    – Lưu lại.
  4. Tạo Roles:
    – Vào menu Realm roles -> Create role.
    – Tạo 2 role: USERADMIN.

Phần B: Lập trình Spring Boot (Resource Server)
Bước 1: Dependencies (pom.xml) Cần Java 17+ và Spring Boot 3.x.

XML
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thư viện quan trọng nhất để biến App thành Resource Server -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Bước 2: Cấu hình (application.yml) Khai báo địa chỉ của Keycloak để Spring Boot biết nơi xác thực token.

YAML
server:
  port: 5000 # Chạy port khác Keycloak

spring:
  application:
    name: keycloak-integration-demo
  security:
    oauth2:
      resourceserver:
        jwt:
          # Đường dẫn Issuer của Realm (Quan trọng)
          # Cấu trúc: http://<host>:<port>/realms/<realm-name>
          issuer-uri: http://localhost:8080/realms/springboot-demo
          # jwk-set-uri sẽ được tự động cấu hình từ issuer-uri

Bước 3: Class SecurityConfig.java (Quan trọng nhất) Mặc định Keycloak trả về role trong JSON phức tạp, ta cần convert nó về dạng ROLE_ABC để Spring Security hiểu.

Java
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import 
org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Cho phép dùng @PreAuthorize ở Controller
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // Tắt CSRF cho API
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll() // API public không cần token
                .anyRequest().authenticated() // Các API còn lại yêu cầu phải login
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
            );
        
        return http.build();
    }

    // Converter: Biến đổi thông tin từ JWT Keycloak sang Spring Security Authority
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(source -> {
            // Lấy map roles từ claim "realm_access" của Keycloak
            Map<String, Object> realmAccess = source.getClaimAsMap("realm_access");
            
            if (realmAccess == null || !realmAccess.containsKey("roles")) {
                return List.of();
            }

            List<String> roles = (List<String>) realmAccess.get("roles");
            
            // Convert: [USER, ADMIN] -> [ROLE_USER, ROLE_ADMIN]
            return roles.stream()
                    .map(role -> new org.springframework.security.core.authority.SimpleGrantedAuthority("ROLE_" + role))
                    .collect(Collectors.toList());
        });
        return converter;
    }
}

Bước 4: Class DemoController.java

Java
package com.example.demo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/public/hello")
    public String hello() {
        return "Public: Xin chào, ai cũng xem được!";
    }

    @GetMapping("/user/profile")
    @PreAuthorize("hasRole('USER')")
    public String userProfile() {
        return "Secured: Đây là trang dành cho User có role USER.";
    }

    @GetMapping("/admin/dashboard")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminDashboard() {
        return "Secured: Đây là trang quản trị (ADMIN ONLY).";
    }
}

4. RESULT: Kiểm thử hoạt động

Sử dụng Postman để demo luồng OAuth2.

Bước 1: Lấy Token từ Keycloak (Login)
URL: http://localhost:8080/realms/springboot-demo/protocol/openid-connect/token
Body (x-www-form-urlencoded):
+ client_id: my-backend-api
+ client_secret: (Lấy trong Keycloak: Clients -> my-backend-api -> Credentials)
+ grant_type: password
+ username: testuser
+ password: 123
Kết quả: Bạn nhận được JSON chứa access_token. Copy chuỗi này.

Bước 2: Gọi API bảo mật
URL: http://localhost:8081/user/profile
Authorization Tab: Chọn type Bearer Token và dán access token vào.
Kết quả:
+ Nếu token hợp lệ: Trả về Secured: Đây là trang dành cho User... (Status 200).
+ Nếu không gửi token: 401 Unauthorized.

Kết luận: Bạn đã xây dựng thành công một hệ thống bảo mật hiện đại, tách biệt hoàn toàn Resource Server và Authorization Server, tuân thủ chuẩn OAuth2.

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like