Trong bài viết này tôi hướng dẫn các bạn cấu hình LDAP với Spring Boot:
Công cụ và thư viện được sử dụng trong bài viết:
- Spring boot 2.7.4
- Spring tool suite 4
- Spring data elasticsearch 4.4.2
- Maven 3
- Java 11
- Elasticsearch 7.17.6
- Kaizen Elastic
- Apache Directory Studio(2.0.0-M17)
- ApacheDS(2.0.0.AM26)
1. Cấu trúc project:
2. Cấu trúc file pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>ldap</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ldap</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-ldap</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.ldap</groupId> <artifactId>spring-ldap-core</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3. Apache Directory Studio:
Đầu tiên, cần tải về "Apache Directory Studio". Link download:
https://directory.apache.org/studio/download/download-windows.html
Ở đây tôi tải bản mới nhất cho window, tôi chọn file mở rộng là ".exe"
Sau khi cài đặt thành công Apache Directory Studio -> mở ứng dụng lên. Nếu bạn nào gặp thông báo lỗi "Incompatible JVM" thì có nghĩa là hiện tại phiên bản java các bạn đang sử dụng thấp hơn yêu cầu mà Apache Directory Studio yêu cầu. Phiên bản mới nhất yêu cầu Java tối thiểu là version 11
Để giải quyết vấn đề này các bạn cần cài đặt Java version 11 trở lên, ở đây tôi tải bản Java 11 và chọn file mở rộng là ".exe":
Link download: https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html
Sau khi cài đặt thành công -> đường dẫn mặc định là: "C:\Program Files\Java\jdk-11.0.16\bin"
Các bạn tiến hành cập nhật lại biến môi trường cho Java phiên bản mới, xong mở Command Prompt lên và kiểm tra version đúng chưa:
Nhưng nếu bạn nào gặp vấn đề sau khi nâng cấp thành công, kiểm tra version cũng thấy đã cập nhật đến Java phiên bản mới nhưng khi mở ứng dụng Apache Directory Studio vẫn bán lỗi "Incompatible JVM" thì xử lý như sau:
Các bạn vào thư mục của Apache Directory Studio, mặc định là: "C:\Program Files\Apache Directory Studio"
Trong thư mục Apache Directory Studio, mở file "ApacheDirectoryStudio.ini", tìm tới dòng:
#-vm
#/usr/lib/jvm/java-11-openjdk/bin/java
Cập nhật lại dòng "/usr/lib/jvm/java-11-openjdk/bin/java" -> đường dẫn chứa Java:
"/usr/lib/jvm/java-11-openjdk/bin/java" -> "C:\Program Files\Java\jdk-11.0.16\bin"
Sau khi cập nhật lại đường dẫn -> bỏ comment ra để sử dụng
Tôi mở Apache Directory Studio lên:
=> Mở ứng dụng thành công
Tiếp theo, tôi tạo 1 new "Connection", trong mục "Connections" click phải chuột và chọn "New Conection...":
Tại giao diện "Connection" -> cấu hình các mục như bên dưới:
- Connection name: Tuỳ ý. Ở đây tôi để là "LdapProject"
- Hostname: Tôi để "localhost"
- Port: Tôi để "10389"
- Connection timeout (s): Tôi để "300"
Giao diện "Authentication":
- Bind Dn or user: Tôi để "uid=admin,ou=system"
- Password: Tôi để "secret"
Tiếp theo, click "Finish" -> nó sẽ báo lỗi "cannot_connect_to_server" lý do là chưa cài đặt ApacheDS, tiến hành cài đặt ApacheDS:
Link download: https://directory.apache.org/
Ở đây tôi tải bản mới nhất version "2.0.0.AM26", tôi chọn tải bản ".zip":
Sau khỉ tải về, tiến hành giải nén và trong thư mục vừa giải nén tìm đến file "apacheds.bat: -> run file này để start:
Sau khi start xong, quay lại giao diện LDAP, click phải chuột vào "Connection" vừa tạo -> chọn "Open connection" hoặc double click vào tên connection
=> Start thành công
Sau khi chuẩn bị xong cho phần cài đặt Apache Directory Studio, tôi qua tiếp phần cấu hình Ldap trong Spring Boot
4. Nội dung file application:
Ở đây, tôi có enable SSL để cấu hình sử dụng HTTP/2, trong bài viết này không cần cấu hình SSL, các bạn có thể tham khảo làm thế nào để enable SSL và HTTP/2:
5. Package "com.example.ldap.application":
6. Package "com.example.ldap.model":
Trong package này tôi tạo 1 class "User":
Nội dung class "User": Class này tôi sử dụng để khai báo repository cho Elasticsearch
7. Package "com.example.ldap.repository":
Trong package này tôi tạo 1 class "UserESRepository" :
**Nội dung class "UserESRepository": **
8. Package "com.example.ldap.service":
Nội dung class "ILdapService":
Nội dung class "LdapService":
9. Package "com.example.ldap.controller":
Trong controller, đầu tiên tôi tạo 1 mapping để load page đăng nhập:
Tiếp theo, tôi cần tạo 1 mapping "/createUser" để xử lý việc thêm mới user và lưu dữ liệu trong Elasticsearch database và Ldap. Tại Ldap tôi cũng lưu những thông tin cơ bản của user gồm "userId - firstName - lastName - email - password". Tôi không lưu thông tin password trong database ES nữa, thay vào đó tôi dùng Ldap để quản lý thông tin password
Phương thức "createUser" trong LdapService:
Trước tiên, tôi giới thiệu qua 1 vài thuộc tính phổ biến trong Ldap mà tôi sử dụng trong bài viết này:
-
CN(Common Name): Firstname + Lastname và thuộc tính này là bắt buộc
-
DN(distinguishedName): Trong file application.xml tôi có khia báo 1 thuộc tính "spring.ldap.base" với giá trị mặc định là "dc=example,dc=com" -> đây là DN. Ví dụ, tôi có 1 "ou=users" -> DN sẽ là "ou=users,dc=example,dc=com"
-
SN(Lastname)
-
OU(Organizational unit): Tôi lấy ví dụ đơn giản như này. Bạn tạo 1 OU là "useraccounts" trong "dc=example,dc=com" và trong OU đó quản lý nhiều tài khoản user
dc=example,dc=com ou=useraccounts cn=user001
Tôi đi phân tích những phần chính trong phương thức "createUser":
Trong đoạn code ở trên, tôi khai báo đường dẫn DN mà tôi sẽ thêm user vào:
dc=example,dc=com ou=users cn=user001(đây là user tôi sẽ tạo)
Trong đoạn code trên, tôi thêm thuộc tính "objectclass" là "person", tôi lấy ví dụ trực tiếp trên Apache Directory Studio cho dễ hiểu:
Tại Apache Directory Studio, tại màn hình "Attribute Description" tôi phải chuột và chọn "New Attribute...":
Tại mục "Attribute type" tôi chọn "objectClass" -> click "Finish":
Tại mục "Available object classes" tôi chọn "person" như trong code tôi khai báo và tôi click "Add" -> click "Next":
Tại giao diện "Attributes", các bạn thấy có thêm 2 thuộc tính cần phải nhập dữ liệu là "CN" và "SN" nên trong phương thức "createUser" tôi có thêm 2 thuộc tính đó, giá trị "CN" tôi lấy theo giá trị userId
Tiếp theo, tôi lấy ví dụ cho 1 loại object class khác là "account", quay về giao diện "Object classes" tôi remove "person" đã thêm trước đó ra và chọn "account" -> click "Add" -> click "Next":
=> Các bạn thấy, với object class là "account" -> nó không có 2 thuộc tính là bắt buộc là "CN" và "SN" thay vào đó là thuộc tính "UID", nên tuỳ theo lạoi objectClass mà cần phải khai báo các thuộc tính bắt buộc cho phù hợp
Trong phương thức "createUser" tôi có khai báo thuộc tính "userPassword", thuộc tính này dùng để lưu thông tin password user và tôi đã có mã hoá password truyền vào với phương thức "encodeToSHA":
Trong class "LdapService" tôi sử dụng phương thức "getUserLdap" dùng để lấy ra thông tin user lưu trong Ldap dựa vào DN:
Tôi sử dụng "ldapTemplate.lookup(dn)" tìm kiếm theo DN -> nó sẽ trả về 1 single object nếu tìm thấy, ví dụ tôi đang có thông tin user001 như bên dưới:
dc=example,dc=com ou=users cn=user001
Nếu tôi muốn tìm user001 -> DN lúc này là "cn=user001,ou=users,dc=example,dc=com" nghĩa là tìm trong "dc=example,dc=com" trong "ou=users" có thông tin user nào với "cn=user001" hay không
Trong controller, tôi tạo phương thức "getUserLdap":
Tiếp theo, tôi quay lại Controller với mapping "/createUser":
Trong phương thức này, tôi lưu thông tin user đến ES và Ldap
Thông tin password tôi thiết lập theo mẫu: "P@ssword" + birthDate(yyyyMMdd), ví dụ birthDate của user là "01-01-1986" -> password sẽ là "P@ssword19860101"
Bây giờ, tôi tiến hành kiểm tra với kịch bản tạo 1 user với nội dung:
{ "userId":"user001", "firstName":"visible", "lastName":"mask", "email": "abc@example.com", "address": "tphcm", "birthDate":"1986-01-01
}
Tôi dùng Postman call api "https://localhost:8080/createUser", method là "POST":
=> Kết quả không tạo được user
Tôi kiểm tra console log, thấy báo lỗi này:
Nguyên nhân gây ra lỗi này là do trong code tôi có khai báo "ou=users" nhưng hiện tại kiểm tra trong DN "dc=example,dc=com" không có "ou=users".Tôi chạy Apache Directory Studio kiểm tra lần nữa:
=> Không có "ou=users" trong DN "dc=example,dc=com"
Vậy tôi chỉ cần tạo 1 "ou=users" là xong, tôi giới thiệu 2 cách để thêm "ou=users":
Cách 1: Nhanh nhất, các bạn thấy mặc định có "ou=system", bung cây thư mục ra và tìm đến bất kỳ ou nào, ở đây tôi thấy có "ou=users" -> copy entry này và paste đến DN "dc=example,dc=com":
Khi paste entry vào, sẽ có thông báo hỏi, các bạn chọn option đầu tiên:
Như vậy, đã có "ou=users" trong DN "dc=example,dc=com":
Cách 2: Tạo 1 file định dạng "*.ldif" với nội dung:
Tại Apache Directory Studio -> chọn File -> New hoặc nhấn tổ hợp phím "Ctrl + N".Tại giao diện "Select a wizard" -> bung cây thư mục "LDAP Bowser" ra và chọn "LDIF File" -> click "Finish":
Tôi tạo nội dung như bên dưới và save file này
dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
ou: users
Tôi khai báo "ou=users" nghĩa là khi tôi import file này đến DN "dc=example,dc=com" -> "ou=users" sẽ được tạo, các bạn có thể tuỳ ý tạo ou với tên khác nhau
Để import ldif file, click phải chuột vào DN "dc=example,dc=com" -> chọn "Import" -> chọn "LDIF Import..." -> chọn đến nơi chứa file ldif vừa tạo -> click "Finish":
Các bạn cần chú ý 1 vấn đề, nếu những lần sau các bạn import lại chính file trước đó đã import -> sẽ báo lỗi và lúc này chỉ cần check chọn option "Overwrite existing logfile" rồi click "Finish":
Tôi call api "https://localhost:8080/createUser" để kiểm tra:
=> Tạo user thành công trong Es và Ldap
Kiểm tra trong Elasticsearch:
Kiểm tra trong Ldap, để reload entry nhấn "F5" hoặc chuột phải DN -> chọn "Reload Entry":
=> user001 được lưu thành công đến Ldap
Sau khi tạo user thành công, tôi xử lý tiếp cho phương thức "checkInfoUserLogin" trong controller:
Trong phương thức "checkInfoUserLogin" tôi có sử dụng phương thức "authenticate" từ class "LdapService" để kiểm userId với password có trong Ldap không:
Để kiểm tra, tôi phải khai báo đường dẫn DN, ở đây DN là "cn={userId},ou=users,dc=example,dc=com"
Tôi thử đăng nhập trang web với thông tin userId: "user001" và password là "P@ssword19860101":
=> Đăng nhập thành công và redirect đến dashboard
Tôi kiểm tra với kịch bản tạo mới với userId đã có:
=> Tạo mới không thành công
Kiểm tra tiếp với kịch bản tạo 1 user khác, ở đây tôi tạo "user002":
{ "userId":"user002", "firstName":"mr", "lastName":"bear", "email": "bear@example.com", "address": "tphcm", "birthDate":"1988-04-01" }
=> Tạo mới thành công
Kiểm tra trong ES:
=> Lưu data thành công
Kiểm tra trong Ldap:
=> Lưu data thành công
Tôi thử đăng nhập với userId là user002:
=> Đăng nhập thành công