利用Docker部署管理LDAP及其初次使用

李明發表於2023-01-12

前言:
本週主要寫了gitlabWebhook轉github的專案,總體上沒有遇到什麼大問題,這周接觸到了LDAP,於是就花時間實際操作了解了一下。

一、什麼是LDAP

LDAP是輕量級目錄訪問協議的簡稱。
目錄是一個為查詢、瀏覽和搜尋而最佳化的資料庫,它成樹狀結構組織資料,類似檔案目錄一樣。

二、為什麼要用LDAP

我們利用LDAP要達到的最終目的就是實現統一登入,比如我們有多個系統,每個系統都是獨立執行,但是我們想要為了方便管理想要把這些使用者全存在一起並且這些系統登入時統一向這個庫進行查詢。
那麼這時候我們就用到了LDAP。

三、用docker部署LDAP和LDAP視覺化介面

1、安裝openldap


docker run \
    -d \
    -p 389:389 \
    -p 636:636 \
    -v /usr/local/ldap:/usr/local/ldap \
    -v /data/openldap/ldap:/var/lib/ldap \
    -v /data/openldap/slapd.d:/etc/ldap/slapd.d \
    --env LDAP_ORGANISATION="imysh" \
    --env LDAP_DOMAIN="imysh.com" \
    --env LDAP_ADMIN_PASSWORD="123456" \
    --name openldap \
    --hostname openldap-host\
    --network bridge \
    osixia/openldap

其中 -p 389:389 \ TCP/IP訪問埠
-p 636:636 \ SSL連線埠。
–network bridge 連線預設的bridge網路(docker0)
–hostname openldap-host 設定容器主機名稱為 openldap-host
–env LDAP_ORGANISATION=“imysh” 配置LDAP組織名稱
–env LDAP_DOMAIN=“imysh.com” 配置LDAP域名
–env LDAP_ADMIN_PASSWORD=“123456” 配置LDAP密碼
預設登入使用者名稱:admin

2、安裝視覺化介面phpidapadmin

docker run \
    -p 8081:80 \
    --privileged \
    --name phpldapadmin \
    --env PHPLDAPADMIN_HTTPS=false \
    --env PHPLDAPADMIN_LDAP_HOSTS=192.168.x.xxx  \
    --detach osixia/phpldapadmin

其中 PHPLDAPADMIN_LDAP_HOSTS 為配置openLDAP的IP或域名,我們是在本地起的,所以填寫我們本機IP即可。
查詢本機IP:
ifconfig | grep "inet"
image.png

儲存方式

不像我們熟知的關聯式資料庫, 資料都在表中,一行一行的,一目瞭然,這個OpenLDAP是以樹的方式儲存的。 比如一個人的資訊是這樣的:
image.png
名詞解釋:
dc:域組織(可以把它當作關係型中的庫)

比如將 http://liming.tcom這樣的域名,可以拆成 dc=liming,dc=com這樣的形式。

ou:指代組織單元、部門
cn:通用名稱,一般為使用者名稱
dn:用來唯一標識一項的路徑。(一條dn就類似於關係型資料庫中的一條資料)
例:
image.png
比如這個張三的dn:cn=zhang san,cn=newgroup,dc=imysh,dc=com
image.png

訪問管理首頁

訪問: 127.0.0.1:8081
image.png
用我們初始化的管理員賬號登入
賬號:cn=admin,dc=imysh,dc=com
密碼:123456

新建部門、組和使用者

新建部門(ou):
image.png
image.png

新建組(cn)
image.png

新建使用者
image.png
image.png
我們可以在新建使用者時配置他的姓氏名稱以及所屬組、使用者名稱、密碼、加密方式等資訊。

四、SpringBoot整合LDAP

所用依賴:

<dependency>
            <groupId>com.sun</groupId>
            <artifactId>ldapbp</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>compile</scope>
        </dependency>

配置:

/**
 * LDAP 的自動配置類
 *
 * 完成連線 及LdapTemplate生成
 */
@Configuration
public class LdapConfiguration {

    private LdapTemplate ldapTemplate;

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap();

        contextSource.setUrl(LdapConstans.url);
        contextSource.setBase(LdapConstans.BASE_DC);
        contextSource.setUserDn(LdapConstans.username);
        contextSource.setPassword(LdapConstans.password);

        //  解決 亂碼 的關鍵一句
        config.put("java.naming.ldap.attributes.binary", "objectGUID");

        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        return contextSource;
    }
 
    @Bean
    public LdapTemplate ldapTemplate() {
        if (null == ldapTemplate)
            ldapTemplate = new LdapTemplate(contextSource());
        return ldapTemplate;
    }

}


class LdapConstants {
  static String url = "ldap://192.168.31.73";
  static String BASE_DC = "dc=imysh,dc=com";
  static String username = "cn=admin,dc=imysh,dc=com";
  static String password = "123456";
}

透過上面的程式碼,在IOC容器完成bean的定義,我們在外部就可以注入使用LdapTemplate了

LdapTemplate完成CRUD功能:

建立person類:


@Data
@Entry(base = "cn=group,ou=department", objectClasses="inetOrgPerson")
public class Person {

  /**
   * 此欄位不能少
   */
  @Id
  private Name id;

  @DnAttribute(value = "uid", index = 3)
  private String uid;

  @Attribute(name = "cn")
  private String commonName;

  @Attribute(name = "sn")
  private String myUerName;

  private String userPassword;

}

問題:這些對應LDAP的屬性(sn、cn)是從哪來的?
image.png

新增person

 @Test
  public void addPerson() {
    Person person = new Person();
    person.setUid("uid:15");
    person.setMyUerName("liMing");
    person.setCommonName("liming");
    person.setUserPassword("123456");
    ldapTemplate.create(person);
  }

結果:
image.png

查詢person

@Test
public void getUsers() {
    String filter = "(&(objectclass=inetOrgPerson))";
    List<Person> list = ldapTemplate.search("cn=group,ou=department", filter, new AttributesMapper() {
      @SneakyThrows
      @Override
      public Object mapFromAttributes(Attributes attributes) throws NamingException {
        // 可以透過下面方法列印屬性
        NamingEnumeration<? extends Attribute> att = attributes.getAll();
        while (att.hasMore()) {
          Attribute a = att.next();
          System.out.println(a.getID() + "=" + a.get());
        }
        Person user = new Person();

        Attribute a = attributes.get("sn");
        if (a != null) user.setMyUerName((String)a.get());

        a = attributes.get("userPassword");
        if (a != null) user.setUserPassword(a.get().toString());

        a = attributes.get("cn");
        if (a != null) user.setCommonName((String) a.get());
        return user;
      }
    });
    System.out.println(list);
  }

對於其中的filter要注意filter整體要在括號裡,否則會報如下錯誤:

org.springframework.ldap.InvalidSearchFilterException: invalid attribute description; nested exception is javax.naming.directory.InvalidSearchFilterException: invalid attribute description; remaining name 'cn=group,ou=department'
  • 獲取域列表,如果要獲取確定的某一個域,filter可以這樣寫: (&(objectclass=dcObject)&(dc=imysh))
  • 獲取組織列表,如果要獲取確定的某一個組織,filter可以這樣寫: (&(objectclass=organizationalUnit)&(ou=people)
  • 獲取people列表,如果要獲取某一個確定的人,filter可以這樣寫: (&(objectclass=inetOrgPerson)&(uid=uid:13))

結果:
image.png
image.png

更新

  @Test
  public void update(){
    String oldPersonDn = " uid=uid:15,cn=group,ou=department";
    Person newPerson = new Person();
    newPerson.setMyUerName("newLiming");
    newPerson.setCommonName("newCommentName");
      ldapTemplate.modifyAttributes(oldPersonDn, new ModificationItem[] {
          new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("cn", newPerson.getCommonName().trim())),
          new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("sn", newPerson.getMyUerName().trim())),
      });
  }

更新就是替換屬性,使用ModificationItem類進行處理。
就像我們之前使用的資料庫一樣,最簡單的就是透過ID來進行更新,也就相當於這裡的dn
結果:
image.png

刪除

@Test
  public void delete() {
    String PersonDn = " uid=uid:15,cn=group,ou=department";
    ldapTemplate.unbind(PersonDn);
  }

結果:
image.png
刪除也就是解綁的過程,直接呼叫unbind即可

總結:LDAP主要是為了進行使用者的統一認證,但是由於時間限制並沒有測試到這裡,但是總體上感覺起來LDAP並沒有想象中複雜,主要是見到了這種樹形儲存的形式,實際上手操作後會感覺清晰很多。

相關文章