Spring Boot интегрирует LDAP для реализации входа

Spring Boot задняя часть база данных Spring

Введение в LDAP

  • LDAP (облегченный протокол доступа к каталогам) — это реализация, предоставляющая информационные службы, называемые службами каталогов.

    Служба каталогов — это специальная система баз данных, специально оптимизированная для операций чтения, просмотра и поиска. Каталоги обычно используются для хранения описательной информации на основе атрибутов и для поддержки сложных возможностей фильтрации. Каталоги обычно не поддерживают сложное управление транзакциями или стратегии отката, которые требуются базам данных общего назначения для крупномасштабных операций обновления. Обновления служб каталогов, как правило, очень просты. Такие каталоги могут хранить различную информацию, включая личную информацию, веб-ссылки, изображения в формате jpeg и т. д. Чтобы получить доступ к информации, хранящейся в каталоге, необходимо использовать протокол доступа, работающий поверх TCP/IP – LDAP.

    Информация в каталоге LDAP организована в соответствии с древовидной структурой, а конкретная информация хранится в структуре данных записи. Запись эквивалентна записи таблицы в реляционной базе данных; запись — это атрибут с отличительным именем (DN), которое используется для ссылки на запись, а DN эквивалентно первичному ключу в таблице реляционной базы данных. Атрибут состоит из типа (Type) и одного или нескольких значений (Values), что эквивалентно полю (Field) в реляционной базе данных, состоящей из имени поля и типа данных, только для удобства поиска, Тип в LDAP может иметь несколько значений, а не в реляционной базе данных, требующей, чтобы поля, реализованные для уменьшения избыточности данных, не были связаны между собой. Организация записей в LDAP обычно организована в соответствии с географическим положением и организационными отношениями, что очень интуитивно понятно. LDAP хранит данные в файлах и может использовать файловые базы данных на основе индексов вместо реляционных баз данных для повышения эффективности. Примером типа может быть почта, значением которой будет адрес электронной почты.

    Информация LDAP хранится в древовидной структуре.Страна (c=CN) или доменное имя (dc=com) обычно определяется в корне дерева, а одна или несколько организаций (o=Acme) или организационные подразделения ( у=люди). Организационная единица может содержать такую ​​информацию, как все сотрудники, все принтеры в здании и т. д. Кроме того, LDAP поддерживает контроль над тем, какие атрибуты запись может и должна поддерживать, что достигается с помощью специального атрибута, называемого objectClass. Значение этого атрибута определяет некоторые правила, которым должна следовать запись, которые указывают, какие атрибуты запись может и, по крайней мере, должна содержать. Например: объектный класс inetorgPerson должен поддерживать sn(фамилия) и cn(общий) имя), но также может содержать необязательные атрибуты, такие как электронная почта, номер телефона и т. д.

Соответствие короткого имени LDAP

o:organization(组织-公司)
ou:organization unit(组织单元-部门)
c:countryName(国家)
dc:domainComponent(域名)
sn:surname(姓氏)
cn:common name(常用名称)
sAMAccountName:英文名(RTX账号,唯一)
userPrincipalName:登陆用户名 和 英文名一致

Вышеприведенный контент взят из:Быстрый старт LDAP

Реализация входа

  • Создайте базовый проект Spring Boot
  • Внедрить зависимость Spring Ldap в pom.xml

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-ldap</artifactId>
    </dependency>
    

    spring-boot-starter-data-ldap — это реализация автоматической настройки LDAP, инкапсулированная Spring Boot, основанная на spring-data-ldap для выполнения определенных операций на сервере LDAP.

  • Добавьте следующий файл конфигурации в src/main/resources/application.properties.

    # ldap config
    #连接地址
    spring.ldap.urls=ldap://ldap.rikylee.com:389
    spring.ldap.username=rikylee
    spring.ldap.password=rikylee
    spring.ldap.base=ou=employee,dc=rikylee,dc=com
    spring.ldap.domainName=@rikylee.com
    spring.ldap.referral=follow
    

    Вышеупомянутые параметры являются соответствующей конфигурацией соединения ldap.

  • Создайте пользовательский объект для сопоставления отношений сущностей

    package com.riky.ldap.entity;
    
    import java.util.List;
    
    /**
    * @author Riky Li
    */
    
    public class Person {
      //用户名
      private String name;
      //登录名
      private String sAMAccountName;
      //所属组列表
      private List<String> role;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getsAMAccountName() {
            return sAMAccountName;
        }
    
        public void setsAMAccountName(String sAMAccountName) {
            this.sAMAccountName = sAMAccountName;
        }
    
        public List<String> getRole() {
            return role;
        }
    
        public void setRole(List<String> role) {
            this.role = role;
        }
    
        @Override
        public String toString() {
    
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("name:"+name+",");
            stringBuffer.append("sAMAccountName:"+sAMAccountName);
            if(role!=null && role.size()>0){
                stringBuffer.append(",role:"+String.join(",",role));
            }
    
            return stringBuffer.toString();
        }
    }
    
  • Создайте службу для реализации авторизации входа и запроса информации о пользователе.

    package com.riky.ldap.service;
    
    import com.riky.ldap.entity.Person;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.ldap.core.AttributesMapper;
    import org.springframework.ldap.core.LdapTemplate;
    import org.springframework.ldap.support.LdapUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    import javax.naming.NamingException;
    import javax.naming.directory.Attributes;
    import javax.naming.directory.DirContext;
    import java.util.ArrayList;
    import java.util.List;
    
    import static org.springframework.ldap.query.LdapQueryBuilder.query;
    /**
    * @author Riky Li
    */
    @Service
    public class LdapPersonService {
        @Autowired
        private LdapTemplate ldapTemplate;
    
        @Value("${spring.ldap.domainName}")
        private String ldapDomainName;
    
        public Person findByUsername(String username, String password) throws NamingException {
           //这里注意用户名加域名后缀  userDn格式:abcd@rikylee.com
            String userDn = username + ldapDomainName;
            //使用用户名、密码验证域用户
            DirContext ctx = ldapTemplate.getContextSource().getContext(userDn, password);
            //如果验证成功根据sAMAccountName属性查询用户名和用户所属的组
            Person person = ldapTemplate.search(query().where("objectclass").is("person").and("sAMAccountName").is(username), new AttributesMapper<Person>() {
                @Override
                public Person mapFromAttributes(Attributes attributes) throws NamingException {
                    Person person = new Person();
                    person.setName(attributes.get("cn").get().toString());
                    person.setsAMAccountName(attributes.get("sAMAccountName").get().toString());
    
                    String memberOf = attributes.get("memberOf").toString().replace("memberOf: ", "");
                    List<String> list = new ArrayList<>();
                    String[] roles = memberOf.split(",");
                    for (String role : roles) {
                        if (StringUtils.startsWithIgnoreCase(role.trim(), "CN=")) {
                            list.add(role.trim().replace("CN=", ""));
                        }
                    }
                    person.setRole(list);
    
                    return person;
                }
            }).get(0);
            //关闭ldap连接
            LdapUtils.closeContext(ctx);
    
            return person;
        }
    }
    
  • Создайте модульные тестовые случаи для проверки входа и чтения информации о пользователе.

    package com.riky.ldap.service;
    
    import com.riky.ldap.entity.Person;
    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.test.context.junit4.SpringRunner;
    
    /**
    * @author Riky Li
    * @create 2018-03-20 10:33:18
    * @desciption:
    */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class LdapPersonServiceTest {
        @Autowired
        private LdapPersonService ldapPersonService;
    
        @Test
        public void findByUsernameTest() {
            try {
                Person person = ldapPersonService.findByUsername("rikylee", "rikylee");
                System.out.println("person: " + person.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  • Если возникает следующее исключение

    error code 49
    
    ==========================================================================
    
    javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 52e, vece
    

    Причина: Неверный логин или пароль