Tuesday, May 24, 2022

 

Keycloak và Spring boot - Bắt đầu làm việc với User trong Realm


Note: Chuỗi bài viết chủ yếu mang tính lưu trữ kiến thức cho bản thân và chia sẻ lại cho các bạn. Vì khi mình bắt đầu làm việc với Keycload thì rất khó tìm các hướng dẫn bằng tiếng việt. Bài viết có tham khảo từ nhiều nguồn khác nhau. Thanks !

1. Keycload là gì?

Cơ bản Keycloak là một Open Source Identity và Access Management cho Modern Applications và Services. Dùng để quản lý nhận dạng và truy cập vào các ứng dụng/ dịch vụ.
Về hướng dẫn cài đặt, cách thêm realm, client, role, user trên giao diện web của Keycloak thì các bạn có thể check google hoặc tại đây: Link1 hoặc tại đây: Link2 ( Các bạn ấy viết rất đầy đủ và chi tiết về các thông số hiển thị trên giao diện của Keycload)

2. Vậy đã có hướng dẫn rồi thì mình viết về vấn đề gì?

Nếu một ngày đẹp trời, bạn cho phép một người ( đơn vị) thứ 3 nhảy vào thao tác với ứng dụng Keycload của bạn nhưng lại không muốn cho họ có quyền vào thẳng ứng dụng của bạn để thực hiện.... Chà. Vậy bạn cần 1 ứng dụng/APIs cho họ gọi để thao tác với Keycload. Vậy bạn cần tương tác với Keycload từ code chứ không phải từ giao diện.

3. Nếu đây là vấn đề bạn quan tâm => vậy bắt đầu thôi!

Để bắt đầu, Mình khuyên các bạn nên đọc qua hai bài viết mình đã giới thiệu và tạo được realm, client, role và 1 user cho client.
Chú ý!!!!! Mình đã mất nửa ngày cho vấn đề đơn giản này => Đó là Role Mapping của user

Như ảnh bên dưới, 1 user có 2 kiểu role Realm Roles và Client Roles
Realm Roles : Các bạn sẽ tạo được khi tạo xong Realm
Client Roles: Đây là vấn đề chính. Để user có quyền thao tác và call API của Keycload các bạn phải set client role cho user đó, có rất nhiều loại quyền, các bạn có thể đọc chi tiết.
Ở bài demo này mình sẽ sử dụng quyền realm-management và Available Roles là realm-admin

3.1 Dependency

Để sử dụng Keycload trong Spring boot thì các bạn cần:

  1. Đương nhiên 1 ứng dụng spring cơ bản rồi, Gradle hay Maven đều được
  2. Dependency của Keycload
    Với Gradle các bạn thêm vào file build.gradle
	implementation group: 'org.keycloak', name: 'keycloak-spring-boot-starter', version: '16.1.0'
	implementation group: 'org.keycloak', name: 'keycloak-admin-client', version: '16.1.0'

Với Maven thì các bạn thêm vào file pom

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-spring-boot-starter</artifactId>
    <version>16.1.0</version>
</dependency>
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-admin-client</artifactId>
    <version>16.1.0</version>
</dependency>

Thêm config vào file yml
# Keycloak settings
keycloak:
  realm: DemoKeycloak
  auth-server-url: http://localhost:8080/auth
  ssl-required: none
  resource: demo-app
  use-resource-role-mappings: true
  bearer-only: true
  cors: true
  principal-attribute: preferred_username

3.2 Tạo Keycload Instance


Mình sẽ viết 1 hàm để lấy ra instance của keycload. ***env*** là biến môi trường để lấy ra dữ liệu đã lưu trong file .properties hoặc .yaml ( Dùng Spring thì 100% các bạn thừa biết rồi , cứ vẽ vời thêm chuyện!!!!)
Hàm để lấy ra instance có dạng này Keycloak.getInstance(serverUlr, realm, username, password, clientId, clientSecret)
Các bạn nên viết 1 file utils như này
    @Autowired
    Environment env;
    
    public Keycloak getKeycloakInstance() {
        return Keycloak.getInstance(
                env.getProperty("keycloak.auth-server-url"),
                env.getProperty("keycloak.realm"),
                env.getProperty("keycloak-config.username"),
                env.getProperty("keycloak-config.password"),
                env.getProperty("keycloak.resource"),
                env.getProperty("keycloak-config.client-secret"));
    }

Sau đó các bạn cần dùng ở đâu thì gọi ra thôi
 @Autowired
    KeycloakUtils keycloakUtils;

    public void getListUser(UserRegisterRequest request) {

        Keycloak keycloak = keycloakUtils.getKeycloakInstance();
        .....

3.2 Lấy token ( nếu cần)

Token phục vụ api login

keycloak.tokenManager().getAccessToken().getToken();
keycloak.tokenManager().getAccessToken().getExpiresIn();

3.3 Lấy danh sách users

Lưu ý: các bạn chỉ có thể lấy ra danh sách user của client mà user admin đã dùng để lấy Keycloak instance thôi nhé!!!!


 try {
        keycloak = getKeycloakInstance();
        List<UserRepresentation>   userRepresentations = keycloak.realm(env.getProperty("keycloak.realm")).users().list();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (keycloak != null) {
                keycloak.close();
            }
        }

Quá đơn giản đúng không, các bạn nhớ phải gọi keycloak.close(); để đóng instance sau khi call xong nhé

3.4 Lấy user

Thường thì 1 user sẽ có thể định danh bởi 2 trường, userId và userName


Nếu lấy theo userName:
keycloak = getKeycloakInstance();
res = keycloak.realm(env.getProperty("keycloak.realm")).users().search(request.getParam()).get(0);


Nếu lấy theo userId: các bạn không thể truyền vào id để get trực tiếp như user được, nên ở đây mình lọc ra từ danh sách user luôn ( hơi bị củ chuối):
           UserRepresentation user = new UserRepresentation();
           keycloak = getKeycloakInstance();
           List<UserRepresentation> userRepresentations = keycloak.realm(env.getProperty("keycloak.realm")).users().list();
            for (UserRepresentation lts : userRepresentations) {
                if (lts.getId().equalsIgnoreCase(request.getParam())) {
                    user = lts;
                    break;
                }
            }

3.5 Add User

Để thêm mới một user các bạn làm như sau:
Các bạn cần có 1 model AddUserDTO như sau ( mình xài Lombok nên không có Get/Set j cả nha):


public class AddUserDTO  {
    private String userName;
    private String email;
    private String phoneNumber;
    private String password;
    private String firstname;
    private String lastName;
}

Như các bạn thấy mình có 1 biến statusCode ở cuối cùng, Keycload sẽ trả về status theo chuẩn HTTP để chúng ta dễ hanlling nhé
            keycloak = getKeycloakInstance();
            UsersResource userResource = keycloak.realm(env.getProperty("keycloak.realm")).users();
            CredentialRepresentation credential = Credentials
                    .createPasswordCredentials(request.getPassword());
            UserRepresentation user = new UserRepresentation();
            user.setUsername(request.getUserName());
            user.setFirstName(request.getFirstname());
            user.setLastName(request.getLastName());
            user.setEmail(request.getEmail());
            user.setCredentials(Collections.singletonList(credential));
            user.setEnabled(true);
            Response res = userResource.create(user);
            int statusCode = res.getStatus();
            

3.6 Update User

Để update một user các bạn làm như sau:
Cần Model UpdateUserDTO như sau:

public class UpdateUserDTO  {
   private String userId;
    private String userName;
    private String email;
    private String phoneNumber;
    private String password;
    private String firstname;
    private String lastName;
}

Làm thôi

                UsersResource userResource = keycloak.realm(env.getProperty("keycloak.realm")).users();
                CredentialRepresentation credential = Credentials
                        .createPasswordCredentials(request.getPassword());
                UserRepresentation user = new UserRepresentation();
                user.setUsername(request.getUserName());
                user.setFirstName(request.getFirstname());
                user.setLastName(request.getLastName());
                user.setEmail(request.getEmail());
                user.setCredentials(Collections.singletonList(credential));
                 userResource.get(request.getUserId()).update(user);

3.7 Remove User

           UsersResource userResource = keycloak.realm(env.getProperty("keycloak.realm")).users();
            UserRepresentation user = new UserRepresentation();
            userResource.get(userId).remove();

4 Các thao tác quan trọng khác


Khi thao tác với user không chỉ CRUD là đủ, các bạn cần set quyền hạn ( Role), set trạng thái ( enable/disable), thêm các attribute cho user
Mình sẽ hướng dẫn về trạng thái và attribute, còn role sẽ để bài sau nhé:

Trạng thái của user


Data của khách hàng là cực kì quan trọng, do đó trên thực tế việc remove ít khi được thực hiện, thay vào đó người ta thường chuyển trạng thái hoạt động về disable
Cho nên các bạn chỉ cần set trạng thái của user về disable là user đó sẽ không thể đăng nhập hệ thống được nữa:
 user.setEnabled(false);

Attribute của user

Trên thực tế các thông tin khi tạo mới user bao gồm fistName, lastName, email là không đủ. Các bạn cần thêm một số trường như SĐT, Địa chỉ.......
Vậy các bạn làm như sau (:
Ở bước thêm mới:

//tạo mới mapp Attribute
  Map<String, List<String>> attribute = new HashMap<>();
  //tạo các attribute
  List<String> phone = Arrays.asList(request.getPhoneNumber());
  List<String> mobileDevice = Arrays.asList(request.getMobileDevice());
  
  //đẩy vào Map
  attribute.put(Constants.PHONE_NUMBER, phone);
  attribute.put(Constants.DEVICE, device);
  attribute.put(Constants.MOBILE_DEVICE, mobileDevice);
  
  //set att
  user.setAttributes(attribute);
  

Ở bước Update các bạn cần lấy ra attribute cũ và cập nhật lại giá trị cần xử lý thôi nhé, làm như bước tạo mới là nó clear cả dữ liệu không cần update đó
Note: Kiểu dữ liệu của attribute là varchar(255) 😃 nên các bạn muốn lưu j dài hơn nhớ thay đổi trong db của Keycload nhé

5 Tóm tắt bài viết

Bài viết này mình hướng dẫn các bạn tạo user và các thao tác với user của Kecload thông qua Spring boot. Hi vọng có thể giúp đỡ được các bạn trong quá trình làm việc.
Có sai sót ở đâu các bạn giúp đỡ mình nhé

Tham khảo:
https://huongdanjava.com/vi/keycloak
https://www.baeldung.com/spring-boot-keycloak
https://medium.com/teamarimac/how-to-secure-spring-boot-application-with-keycloak-3ffed7e500a4

No comments:

Post a Comment

So sánh các GitFlow model và áp dụng với CICD

https://medium.com/oho-software/so-s%C3%A1nh-c%C3%A1c-gitflow-model-v%C3%A0-%C3%A1p-d%E1%BB%A5ng-v%E1%BB%9Bi-cicd-b6581cfc893a