1. 引言
由於近期需要開發基於JWT Token的統一身份認證服務項目, 因此需要集成公司原有的AD域實現用戶的身份認證問題, 項目采用Spring Boot框架進行開發, 在此將相應的集成開發步驟進行記錄。
1.1 LDAP簡介
目錄是一個為查詢、瀏覽和搜索而優化的專業分布式數據庫,它呈樹狀結構組織數據,就好象Linux/Unix系統中的文件目錄一樣。目錄數據庫和關系數據庫不同,它有優異的讀性能,但寫性能差,並且沒有事務處理、回滾等復雜功能,不適於存儲修改頻繁的數據。目錄服務是由目錄數據庫和一套訪問協議組成的系統。類似以下的信息適合儲存在目錄中:
企業員工信息,如姓名、電話、郵箱等;
公用證書和安全密鑰;
公司的物理設備信息,如服務器,它的IP地址、存放位置、廠商、購買時間等;
LDAP(Lightweight Directory Access Protocol)是基於目錄服務的輕量目錄訪問協議,它是基於X.500標准而發展起來的,但是它更加簡單,並且可以根據需要定制。與X.500不同,LDAP支持TCP/IP,這對訪問Internet是必須的。
LDAP具有如下特點:
- LDAP的結構用樹來表示,而不是用表格;
- LDAP可以很快地得到查詢結果,不過在寫方面,效率比較差;
- LDAP提供了靜態數據的快速查詢方式;
- 基於Client/Server模型,Server 用於存儲數據,Client提供操作目錄信息樹的工具;
LDAP是一種基於X.500協議的互聯網開放標准,LDAP協議是跨平台的互聯網協議。
LDAP目錄中的信息是按照樹型結構進行組織的,具體信息存儲在條目(Entry)的數據結構中。條目相當於關系數據庫中表的記錄;條目是具有唯一標志名稱DN (Distinguished Name)的屬性(Attribute),DN是用來引用條目的,DN相當於關系數據庫表中的關鍵字(Primary Key)。屬性(Attribute)由類型(Type)和一個或多個值(Values)組成,相當於關系數據庫中的字段(Field)由字段名和數據類型組成,只是為了方便檢索的需要,LDAP中的Type可以有多個Value,而不是關系數據庫中為降低數據的冗余性要求實現的各個域必須是不相關的。LDAP中條目的組織一般按照地理位置和組織關系進行組織,非常的直觀。LDAP把數據存放在文件中,為提高效率可以使用基於索引的文件數據庫,而不是關系數據庫。
LDAP的信息是以樹型結構存儲(如下圖所示)的,在樹根一般定義國家(c=CN)或域名(dc=com),在其下則往往定義一個或多個組織 (Organization)(o=Acme)或組織單元(Organizational units) (ou=People)。一個組織單元可能包含諸如所有雇員、大樓內的所有設備等信息。此外,LDAP支持對條目能夠和必須支持哪些屬性進行控制,這是有一個特殊的稱為對象類別(objectClass)的屬性來實現的。該屬性的值決定了該條目必須遵循的一些規則,其規定了該條目能夠及至少應該包含哪些屬性。例如:inetOrgPerson對象類需要支持sn(surname)和cn(common name)屬性,但也可以包含可選的如郵件,電話號碼等屬性。
1.2 LDAP常見簡稱
簡稱 全稱 用途
o organizaiton 組織/公司
ou Organizaiton Unit 組織單元
c Country 國家
dc Domain Component 域名
sn Suer Name 真實名稱
cn Common Name 常用名稱
dn Distiguished Name 唯一標識名
uid User ID 用戶標識
1.3 AD域與LDAP的區別
Windows AD(Active Directory)域應該是LDAP的一個應用實例,而不應該是LDAP本身。Windows AD域的用戶、權限管理應該是微軟公司使用LDAP存儲了一些數據來解決域控這個具體問題,AD域提供了相關的用戶接口,我們可以把AD域當做微軟定制的LDAP服務器。Active Directory先實現一個LDAP服務器,然后自己先用這個LDAP服務器實現了自己的一個具體應用。
2. Spring Boot集成LDAP配置
在pom.xml中添加Maven依賴
<!-- LDAP Module --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-ldap</artifactId> </dependency> <!-- JPA Module --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
項目依賴包spring-boot-starter-data-ldap是Spring Boot封裝的對LDAP自動化配置的實現,它是基於spring-data-ldap來對LDAP服務端進行具體操作的。
2.1 方法1. 自定義LdapTemplate配置
1. 在項目應用配置文件application.yml中添加AD域配置
# AD Config ldap: url: "ldap://192.168.1.1:389" base: DC=example,DC=com userDn: "administrator@example.com" userPwd: 123456 referral: follow domainName: "%s@example.com"
2. 在Spring Boot中啟動AD域配置
3. 使用LdapTemplate操作LDAP
@Service
public class LdapServiceImpl implements LdapService {
@Autowired
private LdapTemplate ldapTemplate;
@Value("${ldap.domainName}")
private String ldapDomainName;
@Value("${ldap.base}")
private String ldapBaseDn;
/**
* 獲取部門列表
*/
@Override
public List<String> getDepartmentList(String ldapBase, Filter filter) {
return ldapTemplate.search(ldapBase, filter.encode(), new AttributesMapper() {
@Override
public String mapFromAttributes(Attributes attr) throws NamingException {
String distinguishedName = (String)attr.get("distinguishedName").get();
distinguishedName = StringUtils.substringBefore(distinguishedName,ldapBaseDn);
return StringUtils.substringBeforeLast(distinguishedName, ",");
}
});
}
/**
* 獲取用戶列表
*/
@Override
public List<User> getPersonList(String ldapBase, Filter filter) {
return ldapTemplate.search(ldapBase, filter.encode(), new AttributesMapper() {
@Override
public User mapFromAttributes(Attributes attr) throws NamingException {
User person = new User();
String distingugihedName = (String)attr.get("distinguishedName").get();
person.setUserName((String)attr.get("username").get());
person.setEmail((String)attr.get("mail").get());
person.setRealName((String)attr.get("name").get());
if (null != attr.get("mobile")) {
person.setMobile((String) attr.get("mobile").get());
}
if (null != attr.get("telephoneNumber")) {
person.setPhone((String) attr.get("telephoneNumber").get());
}
person.setLdapFlag(1);
String departmentName = StringUtils.substringAfter(distingugihedName.split(",")[1], "OU=");
person.setUnitName(departmentName);
return person;
}
});
}
/*
* 身份認證
*/
@Override
public boolean authenticate(String userName, String password) {
//String userDomainName = getDnForUser(userName);
String userDomainName = String.format(ldapDomainName, userName);
DirContext ctx = null;
try {
ctx = ldapTemplate.getContextSource().getContext(userDomainName,password);
return true;
} catch(Exception e) {
e.printStackTrace();
} finally {
LdapUtils.closeContext(ctx);
}
return false;
}
}
注: 此處沒有使用Spring Data Ldap項目包中的完成自動配置, 而是使用自定義的Ldap操作。
2.2 方法2. 使用Spring Data Ldap自動配置
1. 在項目應用配置文件application.yml中添加AD域配置
使用Spring Data Ldap項目包連接LDAP服務器可以采用以下的配置方式:
spring: ldap: urls: ldap://192.168.1.1:389 base: DC=example,DC=com username: "administrator@example.com" password: 123456
2. 啟用Ldap配置
在Spring Boot主應用程序中添加@EnableLdapRepositories注解
@SpringBootApplication
@EnableLdapRepositories
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}
3. 定義LDAP中屬性與Java中所定義實體的關系映射
@Data
@Entry(base = "ou=XX公司,dc=example,dc=com", objectClasses = {"OrganizationalPerson", "Person", "top"})
public class User {
@Id
private Name id;
@DnAttribute(value = "distiguishedName")
private String distinguishedName;
@Attribute(name = "cn")
private String commonName;
@Attribute(name = "sn")
private String suerName;
@Atrributed(name = "email")
private String email;
... ...
}
4. 創建LDAP對應的DAO操作
/**
* UserDao繼承CrudRepository接口實現基於Ldap的增刪改查操作
*/
public interface UserDao extends CrudRepository<Person, Name> {}
1
2
3
4
通過上面3和4兩個步驟的定義之后,已經將User對象與AD域存儲的內容實現了實體映射關系,我們只需要使用UserDao就可以輕松的對LDAP的相應內容實現讀寫操作。
5. 創建單元測試用例讀取所有用戶的信息
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void findAll() throws Exception {
userDao.findAll().forEach(p -> {
System.out.println("Distigushed Name:" + p.distinguishedName);
});
}
}
參考文獻
Spring Boot中使用LDAP來統一管理用戶信息
Spring LDAP 使用
LDAP服務器的概念和原理簡單介紹
