Spring LDAP 使用方法

Spring LDAP

我累了,感情累了,中午不睡覺,也很累。 -----------Sayi


spring-ldap-core(the Spring LDAP library)java

JNDI/LDAP和JDBC/DB

JNDI是用來作LDAP的編程,正如JDBC是用來SQL編程同樣。儘管他們有着徹底不一樣各有優缺點的API,可是它們仍是有一些共性:spring

  • They require extensive plumbing code, even to perform the simplest of tasks.數據庫

  • All resources need to be correctly closed, no matter what happens.編程

  • Exception handling is difficultapi

Spring JDBC提供了jdbcTemplate等簡便的方式來操做數據庫,Spring LDAP也提供了相似的方式操做LDAP----ldapTemplate。數組

xml 配置(LdapContextSource、ldapTemplate)

<bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
    <property name="url" value="ldap://localhost:10389" />
    <property name="base" value="dc=example,dc=com" />
    <property name="userDn" value="uid=admin,ou=system" />
    <property name="password" value="secret" />
</bean>
<bean id="contextSource"
    class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy">
    <constructor-arg ref="contextSourceTarget" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
    <constructor-arg ref="contextSource" />
</bean>

如上配置後,就能夠在程序中注入ldapTemplate進行操做了。服務器

使用DistinguishedName來構建DN,使用過濾器來更多的限制查詢條件

DistinguishedName類實現了Name接口,提供了更優秀的方法來操做目錄,這個類已通過時了,推薦使用javax.naming.ldap.LdapName這個類。在咱們構建任何操做的時候,均可以使用此類構造基目錄。cookie

private DistinguishedName buildDn() {
        DistinguishedName dn = new DistinguishedName();
        dn.append("ou", "People");
        return dn;
}

一個更復雜的構建以下,查找某個惟一userid的dn,其中使用了過濾器AndFilter:app

protected Name getPeopleDn(String userId) {
    AndFilter andFilter = new AndFilter();
    andFilter.and(new EqualsFilter("objectclass", "person"));
    andFilter.and(new EqualsFilter("objectclass", "xUserObjectClass"));
    andFilter.and(new EqualsFilter("uid", userId));
    List<Name> result = ldapTemplate.search(buildDn(), andFilter.encode(),
            SearchControls.SUBTREE_SCOPE, new AbstractContextMapper() {
                @Override
                protected Name doMapFromContext(DirContextOperations adapter) {
                    return adapter.getDn();
                }
            });
    if (null == result || result.size() != 1) {
        throw new UserNotFoundException();
    } else {
        return result.get(0);
    }

}

除了dn能限制目錄條件外,過濾器提供了關於屬性的查詢限制條件,AndFilter是與的過濾器,EqualsFilter則是相等過濾器,還有不少內置過濾器,如WhitespaceWildcardsFilter、LikeFilter、GreaterThanOrEqualsFilter、LessThanOrEqualsFilter等。ide

稍複雜的AttributesMapper---查詢(search、lookup)

查詢操做有兩個方法,分別是search和lookup,前者在不知道dn的狀況下進行搜索,然後者更像是直接取出對應的Entry。如上的search代碼就是在某個dn的全部子樹(SearchControls.SUBTREE_SCOPE)下搜索符合過濾器條件的DN列表:

List<DistinguishedName> result = ldapTemplate.search(buildDn(),
            filter.encode(), SearchControls.SUBTREE_SCOPE,
            new AbstractContextMapper() {
                @Override
                protected DistinguishedName doMapFromContext(
                        DirContextOperations adapter) {
                    return (DistinguishedName) adapter.getDn();
                }
});

下面的代碼將是直接查出所須要的Entry,其中第二個參數表示要取出的屬性,可選:

public Account queryUser(String userId) {
    return (Account) ldapTemplate.lookup(getPeopleDn(userId), new String[] {
                    "uid", "cn", "objectClass" }, new AccountAttributesMapper());

}
private class AccountAttributesMapper implements AttributesMapper {
    public Object mapFromAttributes(Attributes attrs) throws NamingException {
        Account person = new Account();
        person.setUserID((String)attrs.get("uid").get());
        person.setUserName((String)attrs.get("cn").get());
        person.setDescription((String)attrs.get("description").get());
        return person;
    }
}

AttributesMapper相似與JDBC中的RowMapper,實現這個接口能夠實現ldap屬性到對象的映射,spring也提供了更爲簡單的上下文映射AbstractContextMapper來實現映射,這個類在取ldap屬性的時候代碼更爲簡單和優雅。

更簡單的上下文映射AbstractContextMapper---查詢

如上節所示,咱們已經知道ldap屬性到對象的映射,在咱們查找對象時,咱們可使映射更爲簡單,以下:

public Account queryUser(String userId) {
    return (Account) ldapTemplate.lookup(getPeopleDn(userId), new String[] {
                    "uid", "cn", "objectClass" }, new AccountContextMapper());

}
private class AccountContextMapper extends AbstractContextMapper {

    private String[] ldapAttrId;

    @SuppressWarnings("unchecked")
    public AccountContextMapper() {
        ldapAttrId = buildAttr(userAttrService.getLdapAttrIds(),
                new String[] { "uid", "cn", "objectClass" });
    }

    @Override
    public Object doMapFromContext(DirContextOperations context) {
        Account account = new Account();
        account.setUserId(context.getStringAttribute("uid"));
        account.setUserName(context.getStringAttribute("cn"));
        Map<String, Object> userAttributes = new HashMap<String, Object>();
        // 取賬號元數據的屬性值
        for (String ldapAttr : ldapAttrId) {
            Object value;
            Object[] values = context.getObjectAttributes(ldapAttr);
            if (values == null || values.length == 0)
                continue;
            value = (values.length == 1) ? values[0] : values;
            if (value instanceof String
                    && StringUtils.isEmpty(value.toString()))
                continue;

            userAttributes.put(ldapAttr, value);
        }
        account.setUserAttributes(userAttributes);

        return account;
    }
}

在上面的代碼中,咱們徹底能夠只關注doMapFromContext這個方法,經過參數context讓獲取屬性更爲方便,其中的變量ldapAttrId只是一些額外的用途,標識取哪些屬性映射到對象中,徹底能夠忽略這段代碼。

增長(binding)

插入數據無非咱們要構造這個數據的存儲目錄,和數據屬性,經過上面的知識咱們能夠很輕鬆的構造DN,構造數據咱們採用DirContextAdapter這個類,代碼以下:

DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass",
        userLdapObjectClasses.split(","));
context.setAttributeValue("uid", account.getUserId());
mapToContext(account, context);
ldapTemplate.bind(context);

ldapTemplate.bind(context)是綁定的核心api。
ldap的屬性值也是有類型的,好比能夠是字符串,則經過setAttributeValue來設置屬性值,能夠是數組,則經過setAttributeValues來設置屬性值。其中mapToContext屬於自定義的方法,用來映射更多的對象屬性到LDAP屬性,以下自定義的代碼:

protected void mapToContext(Account account, DirContextOperations ctx) {
    ctx.setAttributeValue("cn", account.getUserName());
    ctx.setAttributeValue("sn", account.getUserName());
    ctx.setAttributeValue("user-account-time",
            getDateStr(account.getLifeTime(), "yyyy/MM/dd"));

    if (StringUtils.isNotEmpty(account.getPassword())) {
        ctx.setAttributeValue("userPassword", account.getPassword());
    }

    Map<String, Object> userAttributes = account.getUserAttributes();
    for (Map.Entry<String, Object> o : userAttributes.entrySet()) {
        String ldapAtt = userAttrService.getLdapAttrId(o.getKey());
        if (ldapAtt == null)
            throw new RuntimeException("Invalid attribute " + o.getKey());
        if (o.getValue() == null)
            continue;
        if (o.getValue() instanceof String
                && StringUtils.isWhitespace(o.getValue().toString())) {
            continue;
        }
        if (ObjectUtils.isArray(o.getValue())
                && !(o.getValue() instanceof byte[])) {
            Object[] array = ObjectUtils.toObjectArray(o.getValue());
            if (array != null && array.length != 0)
                ctx.setAttributeValues(ldapAtt, array);
        } else if (o.getValue() instanceof List) {
            Object[] array = ((List) o.getValue()).toArray();
            if (array != null && array.length != 0)
                ctx.setAttributeValues(ldapAtt, array);
        } else
            ctx.setAttributeValue(ldapAtt, o.getValue());
    }
}

刪除(unbinding)

解綁很是的簡單,直接解綁目錄便可,以下:

ldapTemplate.unbind(dn);

修改(modifying)

修改便是取得對應Entry,而後修改屬性,經過modifyAttributes方法來修改

DirContextOperations context = ldapTemplate
            .lookupContext(dn);
    context.setAttributeValue("user-status", status);
    ldapTemplate.modifyAttributes(context);

rename(重命名或者移動Entry)

Spring 支持移動Entry,經過rename方法,與其同時帶來了一個概念:移動策略或者重命名策略。Spring內置了兩個策略,分別是DefaultTempEntryRenamingStrategy和DifferentSubtreeTempEntryRenamingStrategy,前者策略是在條目的最後增長後綴用來生成副本,對於DNcn=john doe, ou=users, 這個策略將生成臨時DN cn=john doe_temp, ou=users.後綴能夠經過屬性tempSuffix來配置,默認是"_temp",
後者策略則是在一個單獨的dn下存放臨時dn,單獨的dn能夠經過屬性subtreeNode來配置。 策略配置在ContextSourceTransactionManager的事務管理Bean中,屬性名爲renamingStrategy。 如:

<bean id="ldapRenameStrategy" class="org.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy" >
    <constructor-arg name="subtreeNode" value="ou=People,dc=temp,dc=com,dc=cn"></constructor-arg> 
</bean>

上面這麼多,須要在ds服務器支持移動的條件下,不然只能經過刪除--插入來代替移動,以下是個移動的示例:

DirContextAdapter newContext = new DirContextAdapter(newDn);
DirContextOperations oldContext = ldapTemplate.lookupContext(oldDn);
NamingEnumeration<? extends Attribute> attrs = oldContext
            .getAttributes().getAll();
    try {
        while (attrs.hasMore()) {
            Attribute attr = attrs.next();
            newContext.getAttributes().put(attr);
        }
    } catch (NamingException e) {
        throw new RuntimeException("remove entry error:" + e.getMessage());
    }
ldapTemplate.unbind(oldDn);
ldapTemplate.bind(newContext);

分頁支持

很惋惜,有些ldap服務器不支持分頁,而有些已經支持PagedResultsControl,能夠經過cookie來實現與ldap的分頁交互。官方文檔的示例以下:

public PagedResult getAllPersons(PagedResultsCookie cookie) {
    PagedResultsRequestControl control = new PagedResultsRequestControl(PAGE_SIZE, cookie);
    SearchControls searchControls = new SearchControls();
    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
    List persons = ldapTemplate.search("", "objectclass=person", searchControls, control);
    return new PagedResult(persons, control.getCookie());
}

JDBC 和 LDAP 的事務管理

事務在項目中是必須考慮的一部分,這節討論兩種事務的分別處理和結合,經過註解來完成。 典型的JDBC事務配置以下:

<bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 <tx:annotation-driven transaction-manager="txManager" />

咱們配置了jdbc的默認事務管理爲txManager,在服務層咱們可使用註解@Transcational來標註事務。

在單獨須要ldap事務管理時,咱們能夠配置ldap的事務,起了個別名ldapTx:

<bean id="ldapTxManager"
class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager">
    <property name="contextSource" ref="contextSource" />
    <qualifier value="ldapTx"/>
</bean>

咱們可使用註解@Transactional("ldapTx")來標註ldap的事務,若是不想每次引用別名,使用@LdapTransactional,則能夠建立註解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.transaction.annotation.Transactional;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Transactional("ldapTx")
public @interface LdapTransactional {

}

在ldap和jdbc同時都有操做的服務中,咱們能夠配置ContextSourceAndDataSourceTransactionManager來實現事務管理:

<bean id="ldapJdbcTxManager"   
        class="org.springframework.ldap.transaction.compensating.manager.ContextSourceAndDataSourceTransactionManager">  
    <property name="contextSource" ref="contextSource"/>  
    <property name="dataSource" ref="dataSource" />  
    <qualifier value="ldapJdbcTx"/>
</bean>

Object-Directory Mapping (ODM) 簡介

正如數據庫操做中的ORM對象關係映射(表到java對象)同樣,ldap操做也有ODM對象目錄映射。

相關文章
相關標籤/搜索