[Spring Boot] Spring Boot 整合 LDAP 開發教程
Spring Boot 整合 LDAP 開發教程
目錄
簡介
LDAP
(輕量級目錄訪問協議,Lightweight Directory Access Protocol)是實現提供被稱為目錄服務的資訊服務。目錄服務是一種特殊的資料庫系統,其專門針對讀取,瀏覽和搜尋操作進行了特定的優化。目錄一般用來包含描述性的,基於屬性的資訊並支援精細複雜的過濾能力。目錄一般不支援通用資料庫針對大量更新操作操作需要的複雜的事務管理或回捲策略。而目錄服務的更新則一般都非常簡單。這種目錄可以儲存包括個人資訊、web鏈結、jpeg影象等各種資訊。為了訪問儲存在目錄中的資訊,就需要使用執行在TCP/IP 之上的訪問協議—LDAP。
LDAP目錄中的資訊是是按照樹型結構組織,具體資訊儲存在條目(entry)的資料結構中。條目相當於關係資料庫中表的記錄;條目是具有區別名DN (Distinguished Name)的屬性(Attribute),DN是用來引用條目的,DN相當於關係資料庫表中的關鍵字(Primary Key)。屬性由型別(Type)和一個或多個值(Values)組成,相當於關係資料庫中的欄位(Field)由欄位名和資料型別組成,只是為了方便檢索的需要,LDAP中的Type可以有多個Value,而不是關係資料庫中為降低資料的冗餘性要求實現的各個域必須是不相關的。LDAP中條目的組織一般按照地理位置和組織關係進行組織,非常的直觀。LDAP把資料存放在檔案中,為提高效率可以使用基於索引的檔案資料庫,而不是關係資料庫。型別的一個例子就是mail,其值將是一個電子郵件地址。
LDAP的資訊是以樹型結構儲存的,在樹根一般定義國家(c=CN)或域名(dc=com),在其下則往往定義一個或多個組織 (organization)(o=Acme)或組織單元(organizational units) (ou=People)。一個組織單元可能包含諸如所有僱員、大樓內的所有印表機等資訊。此外,LDAP支援對條目能夠和必須支援哪些屬性進行控制,這是有一個特殊的稱為物件類別(objectClass)的屬性來實現的。該屬性的值決定了該條目必須遵循的一些規則,其規定了該條目能夠及至少應該包含哪些屬性。例如:inetorgPerson物件類需要支援sn(surname)和cn(common name)屬性,但也可以包含可選的如郵件,電話號碼等屬性。
LDAP 名詞解釋
o– organization(組織-公司)
ou – organization unit(組織單元-部門)
c - countryName(國家)
dc - domainComponent(域名)
sn – suer name(真實名稱)
cn - common name(常用名稱
配置依賴
compile 'org.springframework.boot:spring-boot-starter-data-ldap'
備註
spring-boot-starter-data-ldap
是Spring Boot
封裝的對LDAP自動化配置的實現,它是基於spring-data-ldap來對LDAP服務端進行具體操作的。
連線
- application.yml
# LDAP連線配置
spring:
ldap:
urls: ldap://10.33.47.7:7003
base: dc=platform,dc=hikvision,dc=com
username: ou=acs,ou=componentaccounts,dc=platform,dc=hikvision,dc=com
password: UlAwRkYl
查詢
-
Person.java
Person中欄位為需要從Ldap中查詢的資料欄位,利用註解@Attribute(name=“xx”)進行註解,Entry中定義的objectClass和base為Ldap中資料資源的定位資訊。查詢的時候可以作為返回物件來接收資料。
/*
* @ProjectName: 程式設計學習
* @Copyright: 2018 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/7/28 18:15
* @email: [email protected]
* @description: 本內容僅限於程式設計技術學習使用,轉發請註明出處.
*/
package com.example.chapter3.spring.components.ldap;
import lombok.Data;
import lombok.ToString;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author xiachaoyang
* @version V1.0
* @date 2018年10月08日 17:16
* @modificationHistory=========================邏輯或功能性重大變更記錄
* @modify By: {修改人} 2018年10月08日
* @modify reason: {方法名}:{原因}
* ...
*/
@Data
@ToString
@Entry(objectClasses = {"bicPersonExt", "bicPerson"}, base = "ou=person,dc=coreservice")
public class Person {
/**
* 主鍵
*/
@Attribute
private String personId;
/**
* 人員姓名
*/
@Attribute(name = "cn")
private String personName;
/**
* 組織ID
*/
@Attribute(name = "orgId")
private String orgId;
/**
* 性別
*/
@Attribute(name = "sex")
private Integer sex;
/**
* 電話
*/
@Attribute(name = "mobile")
private String mobile;
/**
* 郵箱
*/
@Attribute(name = "email")
private String email;
/**
* 工號
*/
@Attribute(name = "jobNo")
private String jobNo;
/**
* 學號
*/
@Attribute(name = "studentId")
private String studentId;
/**
* 證件型別
*/
@Attribute(name = "certType")
private Integer certType;
/**
* 證件號碼
*/
@Attribute(name = "certificateNo")
private String certNo;
@Attribute
protected Date createTime;
/**
* 更新時間
*/
@Attribute
protected Date updateTime;
/**
* 狀態
*/
@Attribute
protected Integer status;
@Attribute
protected Integer disOrder;
/**
* 工作單位
*/
@Attribute
private String company;
}
-
IPersonRepo.java
查詢方法的介面層定義(Dao)
package com.example.chapter3.spring.components.ldap;
import org.springframework.ldap.core.LdapTemplate;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author xiachaoyang
* @version V1.0
* @date 2018年10月08日 15:24
* @modificationHistory=========================邏輯或功能性重大變更記錄
* @modify By: {修改人} 2018年10月08日
* @modify reason: {方法名}:{原因}
* ...
*/
public interface IPersonRepo {
void setLdapTemplate(LdapTemplate ldapTemplate);
List<String> getAllPersonNames();
List<String> getAllPersonNamesWithTraditionalWay();
List<Person> getAllPersons();
Person findPersonWithDn(String dn);
List<String> getPersonNamesByOrgId(String orgId);
}
-
PersonRepoImpl.java
Dao介面實現層
/*
* @ProjectName: 程式設計學習
* @Copyright: 2018 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/7/28 18:15
* @email: [email protected]
* @description: 本內容僅限於程式設計技術學習使用,轉發請註明出處.
*/
package com.example.chapter3.spring.components.ldap;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.query.LdapQuery;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* <p>
*
* </p>
*
* @author xiachaoyang
* @version V1.0
* @date 2018年10月08日 15:24
* @modificationHistory=========================邏輯或功能性重大變更記錄
* @modify By: {修改人} 2018年10月08日
* @modify reason: {方法名}:{原因}
* ...
*/
public class PersonRepoImpl implements IPersonRepo {
private LdapTemplate ldapTemplate;
@Override
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
/**
* 查詢部分欄位集合
* @return
*/
@Override
public List<String> getAllPersonNames() {
return ldapTemplate.search(
query().where("objectclass").is("person"), (AttributesMapper<String>) attrs -> (String) attrs.get("cn").get());
}
/**
* 傳統LDAP查詢方式
* @return
*/
@Override
public List<String> getAllPersonNamesWithTraditionalWay() {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://10.33.47.7:7003/dc=platform,dc=hikvision,dc=com");
env.put(Context.SECURITY_PRINCIPAL, "ou=acs,ou=componentaccounts,dc=platform,dc=hikvision,dc=com");
env.put(Context.SECURITY_CREDENTIALS, "UlAwRkYl");
DirContext ctx;
try {
ctx = new InitialDirContext(env);
} catch (NamingException e) {
throw new RuntimeException(e);
}
List<String> list = new LinkedList<String>();
NamingEnumeration results = null;
try {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
results = ctx.search("", "(objectclass=person)", controls);
while (results.hasMore()) {
SearchResult searchResult = (SearchResult) results.next();
Attributes attributes = searchResult.getAttributes();
Attribute attr = attributes.get("cn");
String cn = attr.get().toString();
list.add(cn);
}
} catch (NameNotFoundException e) {
// The base context was not found.
// Just clean up and exit.
} catch (NamingException e) {
//throw new RuntimeException(e);
} finally {
if (results != null) {
try {
results.close();
} catch (Exception e) {
// Never mind this.
}
}
if (ctx != null) {
try {
ctx.close();
} catch (Exception e) {
// Never mind this.
}
}
}
return list;
}
/**
* 查詢物件對映集合
* @return
*/
@Override
public List<Person> getAllPersons() {
return ldapTemplate.search(query()
.where("objectclass").is("person"), new PersonAttributesMapper());
}
/**
* 根據DN查詢指定人員資訊
* @param dn
* @return
*/
@Override
public Person findPersonWithDn(String dn) {
return ldapTemplate.lookup(dn, new PersonAttributesMapper());
}
/**
* 組裝查詢語句
* @param orgId
* @return
*/
@Override
public List<String> getPersonNamesByOrgId(String orgId) {
LdapQuery query = query()
.base("ou=person,dc=coreservice")
.attributes("cn", "sn")
.where("objectclass").is("person")
.and("orgId").is(orgId);
return ldapTemplate.search(query,(AttributesMapper<String>) attrs -> (String) attrs.get("cn").get());
}
}
-
PersonAttributesMapper
查詢輔助物件
/*
* @ProjectName: 程式設計學習
* @Copyright: 2018 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/7/28 18:15
* @email: [email protected]
* @description: 本內容僅限於程式設計技術學習使用,轉發請註明出處.
*/
package com.example.chapter3.spring.components.ldap;
import org.springframework.ldap.core.AttributesMapper;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
/**
* <p>
*
* </p>
*
* @author xiachaoyang
* @version V1.0
* @date 2018年10月08日 17:17
* @modificationHistory=========================邏輯或功能性重大變更記錄
* @modify By: {修改人} 2018年10月08日
* @modify reason: {方法名}:{原因}
* ...
*/
public class PersonAttributesMapper implements AttributesMapper<Person> {
/**
* Map Attributes to an object. The supplied attributes are the attributes
* from a single SearchResult.
*
* @param attrs attributes from a SearchResult.
* @return an object built from the attributes.
* @throws NamingException if any error occurs mapping the attributes
*/
@Override
public Person mapFromAttributes(Attributes attrs) throws NamingException {
Person person = new Person();
person.setPersonName((String)attrs.get("cn").get());
person.setOrgId((String)attrs.get("orgId").get());
return person;
}
}
- LdapTest.java 測試用例
/*
* @ProjectName: 程式設計學習
* @Copyright: 2018 HangZhou Yiyuery Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/7/28 18:15
* @email: [email protected]
* @description: 本內容僅限於程式設計技術學習使用,轉發請註明出處.
*/
package com.example.chapter3.spring.components;
import com.example.chapter3.Chapter3ApplicationTest;
import com.example.chapter3.spring.components.ldap.Person;
import com.example.chapter3.spring.components.ldap.PersonRepoImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author xiachaoyang
* @version V1.0
* @date 2018年10月08日 11:47
* @modificationHistory=========================邏輯或功能性重大變更記錄
* @modify By: {修改人} 2018年10月08日
* @modify reason: {方法名}:{原因}
* ...
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes={Chapter3ApplicationTest.class})
public class LdapTest {
@Autowired
private LdapTemplate ldapTemplate;
private PersonRepoImpl personRepo;
@Before
public void init(){
personRepo = new PersonRepoImpl();
personRepo.setLdapTemplate(ldapTemplate);
}
@Test
public void ldapRestTestPart1(){
// 查詢所有人員名稱
//personRepo.getAllPersonNames().forEach(p-> System.out.println(p));
//榮禧
//榮耀
//feng_p1
//fengzi_0917_1
//....
// 查詢所有人員集合(指定欄位對映)
//personRepo.getAllPersons().forEach(p-> System.out.println(p.toString()));
//Person(personId=null, personName=fengzi_0917_7, orgId=14ed2744-fbd4-4868-8ebc-6b0b94d5ae60, sex=null, mobile=null, email=null, jobNo=null, studentId=null, certType=null, certNo=null, createTime=null, updateTime=null, status=null, disOrder=null, company=null)
//Person(personId=null, personName=fengzi_0917_104, orgId=14ed2744-fbd4-4868-8ebc-6b0b94d5ae60, sex=null, mobile=null, email=null, jobNo=null, studentId=null, certType=null, certNo=null, createTime=null, updateTime=null, status=null, disOrder=null, company=null)
//根據dn查詢
System.out.println(personRepo.findPersonWithDn("ou=person,dc=coreservice,dc=platform,dc=hikvision,dc=com").toString());
//根據組織ID查詢人員
//personRepo.getPersonNamesByOrgId("14ed2744-fbd4-4868-8ebc-6b0b94d5ae60").forEach(System.out::println);
//feng_0925_4687
//feng_0925_4693
//...
//傳統查詢方式
//personRepo.getAllPersonNamesWithTraditionalWay().forEach(System.out::println);
//榮禧
//榮福
//feng_p1
//fengzi_0917_1
//....
}
}
總結
- LDAP作為輕量級目錄查詢,利用dn的查詢效率十分高
- LDAP十分適合管理帶有樹結構的資料
- LDAP的資料模型(目錄設計)十分重要,關乎到資料冗餘大小、查詢效率等問題。
- LDAP分頁查詢需要針對結果迴圈處理,查詢效率一般。
實際專案開發中LDAP建議只是作為資料同步中心,因為其對於大資料量的查詢效率並不是很高,尤其是資料表變動比較頻繁的情況,LDAP中指定大量條件進行模糊搜尋時,效率很低。
通過定時同步任務來對LDAP的資料和本地資料庫進行同步,然後本地直接操作資料庫表格進行查詢、分頁,此法對LDAP的依賴性降低。
REFRENCES
微信公眾號
掃碼關注或搜尋架構探險之道
獲取最新文章,不積跬步無以至千里,堅持每週一更,堅持技術分享_ !