Как предоставить пользователям вашего программного обеспечения B2B простые и мощные параметры конфигурации

О чем этот пост

В этом посте мы объясним механизмы, которые мы внедрили, чтобы предложить пользователям (обычно разработчикам, системным администраторам или администраторам баз данных) простые и мощные варианты конфигурации. Идеи и архитектура, которые мы рассматриваем, могут быть легко использованы и воспроизведены в любом другом программном обеспечении или среде B2B, требующей настройки. Мы разработали эти принципы на Java, но они универсальны и могут применяться к любому языку, который поддерживает интерфейсы или эквиваленты. Мы не изобретали эти методы (которые в основном основаны на внедрении зависимостей), но мы стремились сделать наши варианты реализации намного проще, чем многие фреймворки. Наши ограничения включали: избегание любого XML для конфигурации и использование только файлов типа properties или ini для настройки параметров.

Контекст

Наша платформа AceQL позволяет пользователям подключаться к базам данных SQL через HTTP. Использование предназначено для разработчиков, которые хотят кодировать заказы SQL непосредственно из мобильного или настольного приложения, которое обращается к удаленной или облачной базе данных. AceQL позволяет этим разработчикам использовать знакомый синтаксис SQL C#, Java или Python в своих программах. Решение упрощает создание простых мобильных или настольных приложений, когда данные уже хранятся в реляционной базе данных SQL.

Например, разработчик Java может напрямую закодировать вызов JDBC, который будет запрашивать удаленную базу данных и возвращать результат через Интернет. (Эта статья не будет охватывать AceQL, поскольку цель состоит только в том, чтобы подробно описать вопросы конфигурации в программном обеспечении B2B. Если вас интересует AceQL, см. пояснения и пример кода здесь: www.aceql.com.)

Самая большая проблема, с которой мы столкнулись, заключалась в том, чтобы убедить разработчиков размещать свои базы данных SQL в Интернете без ущерба для безопасности. Вот почему встроенная безопасность была включена в базовую разработку с самого начала.

Из-за этого ограничения мы хотели предложить два типа конфигурации:

  1. Стандартная конфигурация: простые параметры настройки безопасности без кода (например, установка свойств в файле конфигурации).
  2. Расширенная конфигурация: мощная конфигурация, позволяющая пользователям внедрять собственный код проверки подлинности Java или брандмауэра или даже подключать любое стороннее программное обеспечение брандмауэра, написав всего несколько строк кода.

Необходимость как простых, так и расширенных параметров конфигурации

Зачем предлагать два разных типа конфигурации?

Стандартная конфигурация является обязательной, потому что разработчики заняты: если они тратят время на тестирование нашей платформы, жизненно важно, чтобы они могли быстро и легко создавать конфигурации. В стандартной конфигурации разработчики могут настроить платформу менее чем за 10 минут и приступить к тестированию и оценке.

Расширенная конфигурация также необходима: если наша структура будет использоваться в производстве, пользователи захотят максимально настроить правила аутентификации и брандмауэра, чтобы они могли развертывать их с полной безопасностью и спокойствием (под спокойствием мы подразумеваем пользователям не придется беспокоиться о пробуждающем звонке в 4 часа утра, потому что хакеру удалось удалить некоторые таблицы SQL…)

В следующих строках объясняется, как мы добились этого для процесса аутентификации: цель состоит в том, чтобы аутентифицировать пользователей на стороне клиента (на стороне сервера), которые отправляют свои учетные данные имени пользователя и пароля через вызов HTTP Rest connect API в следующем формате:

https://www.mycompany.com/aceql/database/my_database/username/MyUsername/connect?password=MySecret

Где my_database — это база данных SQL для доступа, а (MyUsername, MySecret) — учетные данные клиента.

(Примечание: connect можно вызывать с методом POST для более надежной защиты паролем.)

Архитектура аутентификации: в начале

Корнем всех параметров конфигурации аутентификации является интерфейс Java, включенный в фреймворк в качестве API и, таким образом, доступный для реализации: UserAuthenticator

Он имеет единственный метод предоставления или отказа в доступе к серверу, просто проверяя имя пользователя и пароль, отправленные клиентской стороной в http через connect API. (В конце концов, IP-адрес клиента также может быть проверен.)

Инструкции по настройке

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

  1. объявить реализацию в файле .properties с условным именем aceql-server.properties,
  2. разверните скомпилированный класс в classpath веб-сервера AceQL.

Объявить имя класса в файле aceql-server.properties очень просто, просто установив значение свойства userAuthenticatorClassName:

# Name of the UserAuthenticator implementation to use:
userAuthenticatorClassName=com.mycompany.MyUserAuthenticator

Как это работает?

Ничего волшебного! При запуске сервера конкретная реализация UserAuthenticator загружается в память: имя класса для загрузки извлекается как property из файлаaceql-server.properties, затем экземпляр класса загружается в память с использованием отражения Java. Таким образом, метод UserAuthenticator.login становится доступным для дальнейших вызовов на стороне клиента.

При каждом входе на стороне клиента учетные данные имени пользователя и пароля отправляются и передаются конкретному методу UserAuthenticator.login. Конечно, если выполнение метода возвращает false, клиенту будет отказано в доступе к серверу AceQL. И если выполнение метода возвращает true, клиентская сторона может начать отправлять операторы SQL на сервер:

Версия 1 наших конкретных классов аутентификации

В версии 1 мы предоставили только реализацию по умолчанию DefaultUserAuthenticator, которая не защищена, но позволяет проводить тестирование AceQL без какого-либо программирования на Java. Он включен в структуру как загруженная по умолчанию реализация UserAuthenticator, если она не указана:

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

Реальная конкретная реализация UserAuthenticator

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

Они напишут реализацию MyLdapUserAuthenticator, которая сверяет имя пользователя и пароль с корпоративным каталогом LDAP. В коде используются стандартные классы JNDI. Импорты были пропущены, некоторые переменные жестко закодированы, а обработка исключений уменьшена, чтобы код оставался читабельным:

Затем пользователь вводит имя класса в файл свойств, развертывает класс на сервере AceQL CLASSPATH и перезапускает сервер AceQL:

# Name of the UserAuthenticator implementation to use:
userAuthenticatorClassName=com.mycompany.MyLdapUserAuthenticator

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

Этот код можно реорганизовать для поддержки любого каталога LDAP: это то, что мы сделали во второй версии.

Версия 2 наших конкретных классов аутентификации

Версия 1 попросит наших пользователей полностью закодировать свою реализацию аутентификации. Но реальный мир предоставляет несколько стандартных механизмов аутентификации, которые поддерживают общие разработки:

Это позволило нам предоставить 4 конкретные реализации UserAuthenticator в программном обеспечении AceQL, которые не требуют кода для развертывания. Необходимые параметры задаются как свойства в файле aceql-server.properties.

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

Вариант 1 можно изобразить так:

Вариант 2 можно изобразить так:

Это может быть улучшено в будущем с помощью новых стандартов аутентификации: NTLM, Kerberos, SSO и т. д.

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

Версия 3 наших конкретных классов аутентификации

Выше мы видели, что если пользователь хочет определить свою собственную реализацию UserAuthenticator, он может сделать это, выполнив следующий процесс:

  • создание собственного конкретного класса, реализующего UserAuthenticator,
  • определение имени класса в файле свойств,
  • развертывание скомпилированного класса на сервере AceQL CLASSPATH.

Такой подход создает проблему: пользовательская реализация тесно связана с сервером AceQL, потому что конкретный класс должен быть в CLASSPATH сервера AceQL при запуске. Это усложняет развертывание, мониторинг, обслуживание, документирование и т. д. Например, если развертывается новая версия AceQL, скомпилированные пользователем классы также необходимо повторно развертывать.

Это можно было бы нарисовать так:

Лучшим подходом для такой жизненно важной реализации (нет ничего более важного, чем процесс аутентификации, верно?) было бы попросить пользователя:

  • определить имя действия для вызова в файле свойств,
  • и больше ничего не спрашивай!

Как мы можем этого добиться? Простое решение — вызвать веб-службу, которая является внешней по отношению к AceQL и разработана организацией, но соответствует некоторым правилам, понятным серверу AceQL.

Веб-сервис должен реализовать следующие функции:

  • Он должен принимать 2 параметра POST username и password.
  • Он должен возвращать:
    — содержимое JSON{"status"="OK"}, если аутентификация прошла успешно.
    — содержимое JSON {"status"="FAIL"}, если аутентификация не удалась.

URL-адрес веб-службы должен быть добавлен в файл aceql-server.properties:

# URL of the Authentication Web Service to call to 
# authenticate client users. 
webServiceUserAuthenticator.url=\
   https://www.mycompany.com/aceql-auth-ws

И имя конкретной реализации UserAuthenticator, включенной в фреймворк, — WebServiceUserAuthenticator:

# Name of the UserAuthenticator implementation to use:
userAuthenticatorClassName=WebServiceUserAuthenticator

Теперь у нас есть чистая архитектура при использовании разработанного механизма аутентификации. Метод WebServiceUserAuthenticator.login будет вызывать веб-службу https://www.mycompany.com/aceql-auth-ws при каждой попытке входа на стороне клиента. Если веб-служба возвращает {"status"="OK"}, клиентская сторона будет аутентифицирована для продолжения сеанса.

Код конкретного механизма аутентификации полностью отделен от AceQL.

Это можно было бы нарисовать так:

Окончательный рисунок нашей архитектуры аутентификации версии 3:

Собираем все вместе

Параметры конфигурации должны обеспечивать:

  • Стандартная конфигурация: простая и легкая настройка с помощью свойств или файла Ini. XML никогда не следует использовать.
  • Расширенная конфигурация: мощная конфигурация за счет внедрения зависимостей с использованием интерфейсов для определения. Это позволяет пользователям вводить свой собственный код или даже подключать стороннее программное обеспечение.

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

Если пользователь хочет ввести свой собственный код для какой-либо важной опции конфигурации, архитектура должна позволять выполнение кода вне программного обеспечения B2B с использованием стандартного механизма связи. Rest-сервисы, отвечающие в формате JSON, являются лучшими кандидатами для установления связи между вашим программным обеспечением B2B2 и кодом пользователя. Цель состоит в том, чтобы обеспечить слабую связь между программным обеспечением B2B и наиболее важным пользовательским кодом (аутентификация, брандмауэр и т. д.): это значительно упрощает обслуживание, мониторинг и документирование.

Javadoc и GitHub/Исходный код

Все описанные интерфейсы и конкретные классы задокументированы в AceQL Javadoc.

Весь исходный код AceQL доступен в нашем GitHub Repository:

Прямой доступ к исходному коду интерфейса UserAuthenticator и предоставленным конкретным реализациям:

Пакет org.kawanfw.sql.api.server.auth в нашем репозитории GitHub