Аутентификация и авторизация с помощью LDAP в GoLang приложении

Для начала немного терминологии:

Интеграция приложения с LDAP (Lightweight Directory Access Protocol) может быть полезна по нескольким причинам. Вот некоторые из них:

1. Централизованная аутентификация

LDAP часто используется для хранения учетных данных пользователей, таких как имена пользователей и пароли. Интегрируя приложение с LDAP, можно обеспечить централизованную систему аутентификации, которая будет управлять доступом ко всем приложениям через единый источник. Это упрощает управление пользователями и их правами доступа.

2. Управление доступом (Access Control)

С помощью интеграции с LDAP можно легко контролировать права доступа пользователей к различным ресурсам и функциям внутри приложения. LDAP поддерживает группы и роли, что позволяет назначать пользователям определенные привилегии на основе их принадлежности к той или иной группе.

3. Синхронизация данных

Интеграция с LDAP позволяет синхронизировать данные между различными системами. Например, изменения в учетной записи пользователя в одной системе могут автоматически обновляться в другой системе, использующей ту же базу данных LDAP.

4. Уменьшение административных затрат

Централизация управления пользователями и их данными в одном месте снижает административную нагрузку. Администраторы могут управлять всеми учетными записями и разрешениями из единого интерфейса, что уменьшает вероятность ошибок и повышает эффективность работы.

5. Безопасность

Использование LDAP для аутентификации и авторизации улучшает безопасность системы, поскольку все учетные данные хранятся в защищенном центральном хранилище. Это также облегчает внедрение политик безопасности, таких как использование сложных паролей, регулярная смена паролей и другие меры защиты.

6. Масштабируемость

LDAP хорошо масштабируется, позволяя добавлять новые пользователи и ресурсы без значительных изменений в инфраструктуре. Это особенно полезно для крупных организаций с большим количеством сотрудников и приложений.

7. Совместимость с другими системами

Многие корпоративные системы уже используют LDAP для управления пользователями и ресурсами. Интеграция нового приложения с существующей системой LDAP обеспечивает совместимость и упрощает интеграцию с другими сервисами организации.

Примеры использования:
  • Корпоративные порталы: Аутентификация и управление доступом к внутренним системам компании.
  • CRM-системы: Управление доступом клиентов и партнеров к различным функциональным возможностям.
  • Облачные сервисы: Обеспечение безопасного доступа к облачным ресурсам и данным.
  • Образовательные платформы: Управление учётными записями студентов и преподавателей, контроль доступа к учебным материалам.

Таким образом, интеграция с LDAP предоставляет множество преимуществ, связанных с безопасностью, управлением пользователями и эффективностью работы информационных систем.

Начнет с того что нам нужно запустить LDAP сервер.

Для этого будем использовать LLDAP — Light LDAP implementation for authentication https://github.com/lldap/lldap

Воспользуемся docker compose. Создадим файл docker-compose.yaml

volumes:
  lldap-data:

services:
  lldap:
    image: lldap/lldap:stable
    ports:
      - 3890:3890
      - 17170:17170
    volumes:
      - lldap-data:/data
    environment:
      - LLDAP_JWT_SECRET=secret
      - LLDAP_KEY_SEED=seed
      - LLDAP_LDAP_BASE_DN=dc=example,dc=com
      - LLDAP_LDAP_USER_PASS=adminPassword

Запустим наш LDAP сервер с помощью команды docker compose up -d

WEB интерфейс будет доступен по адресу http://localhost:17170

Для подключения к LDAP будем использовать библиотеку https://github.com/dlampsi/adc которая в свою очередь является оберткой над библиотекой https://github.com/go-ldap/ldap

Создадим проект для нашего приложения

Добавим зависимости

go get github.com/dlampsi/adc

Ниже приведем код для функции main

package main

import (
	"fmt"
	"github.com/dlampsi/adc"
)

func main() {
	cfg := &adc.Config{
		URL: "ldap://localhost:3890",
		Bind: &adc.BindAccount{
			DN:       "uid=admin,ou=people,dc=example,dc=com",
			Password: "adminPassword",
		},
		SearchBase: "DC=example,DC=com",
	}

	cl := adc.New(cfg)

	if err := cl.Connect(); err != nil {
		fmt.Println(err)
		return
	}

	err := cl.CheckAuthByDN("uid=test,ou=people,dc=example,dc=com", "12345678")

	if err != nil {
		fmt.Println(err)
		return
	}

	user, err := cl.GetUser(adc.GetUserArgs{
		Dn: "test",
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(user)

}

Перед запуском я создал тестового пользователя test с паролем 12345678

Проверяем как выполнится код нашего тестового приложения

go run main.go 

В результате код вывел данные нашего тестового пользователя

&{uid=test,ou=people,dc=example,dc=com  map[givenName:Test mail:test@example.com sn:User] []}

Теперь немного о том, что происходит в коде.

Сначала кратко что такое LDAP и на каких правилах основан этот протокол.

Всякая запись в каталоге LDAP состоит из одного или нескольких атрибутов и обладает уникальным именем (DN — англ. Distinguished Name). Уникальное имя может выглядеть, например, следующим образом: «cn=Иван Петров,ou=Сотрудники,dc=example,dc=com»[1]. Уникальное имя состоит из одного или нескольких относительных уникальных имён (RDN — англ. Relative Distinguished Name), разделённых запятой. Относительное уникальное имя имеет вид ИмяАтрибута=значение. На одном уровне каталога не может существовать двух записей с одинаковыми относительными уникальными именами. В силу такой структуры уникального имени записи в каталоге LDAP можно легко представить в виде дерева.

Запись может состоять только из тех атрибутов, которые определены в описании класса записи (object class), которые, в свою очередь, объединены в схемы (schema). В схеме определено, какие атрибуты являются для данного класса обязательными, а какие — необязательными. Также схема определяет тип и правила сравнения атрибутов. Каждый атрибут записи может хранить несколько значений.

Мы используем lldap — приложение предназначено для аутентификации и авторизации. BaseDN мы указали в docker-compose.yaml файле с помощью переменной окружения LLDAP_LDAP_BASE_DN=dc=example,dc=com

Из README к lldap все пользователи создаются в OU=people, DN администратора по умолчанию cn=admin,ou=people,dc=example,dc=com, пароль для администратора мы задали в переменной окружения LLDAP_LDAP_USER_PASS=adminPassword

Эти параметры мы используем для конфигурации adc:

	cfg := &adc.Config{
		URL: "ldap://localhost:3890",
		Bind: &adc.BindAccount{
			DN:       "uid=admin,ou=people,dc=example,dc=com",
			Password: "adminPassword",
		},
		SearchBase: "DC=example,DC=com",
	}

Далее мы создаем клиент и проверяем подключение:

	cl := adc.New(cfg)

	if err := cl.Connect(); err != nil {
		fmt.Println(err)
		return
	}

Теперь мы можем аутентифицировать тестовго пользователя:

	err := cl.CheckAuthByDN("uid=test,ou=people,dc=example,dc=com", "12345678")

	if err != nil {
		fmt.Println(err)
		return
	}

Если пользователь найден, то идем дальше и получаем информацию о пользователе:

	user, err := cl.GetUser(adc.GetUserArgs{
		Dn: "test",
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(user)

В зависимости от того какие атрибуты установлены для пользователя или в какой группе находится пользователь мы можем разрешать те или иные действия в приложении. Таким образом можно реализовать авторизацию.

Нужно учитывать что если вам требуется более гибкое управление процессом, то стоит обратить внимание на библиотеку https://github.com/go-ldap/ldap

Так же обратите внимание что библиотека https://github.com/dlampsi/adc предназначена для работы с MS Active Directory и реализует некоторые особенности для работы именно с AD.

Всем спасибо, с вами была команда it-story.ru