ActiveMQ поддерживает подключаемые модули JAAS, которые обрабатывают аутентификацию входящих запросов. ActiveMQ поставляется с двумя предварительно загруженными модулями JAAS: модуль, который считывает данные аутентификации из файла свойств, и модуль, в котором данные хранятся в LDAP. Когда MongoDB является хранилищем данных аутентификации, тогда параметры интеграции включают либо синхронизацию хранилища MongoDB с LDAP, либо разработку модуля JAAS для MongoDB. В этой статье описывается модуль MongoDB JAAS для простой схемы данных. Кроме того, предоставляются инструкции по развертыванию этого как области JAAS в Карафе с Blueprint.
Схема
Предполагается очень простая схема:
- Имя пользователя
- Пароль в зашифрованном виде (SHA / SSHA) или в необработанном виде
Модуль JAAS
В модуле входа в систему JAAS нет ничего специфичного для ActiveMQ. Фактически модуль экспортируется как область JAAS в Karaf и может использоваться любой подсистемой, поддерживающей JAAS, такой как сам контейнер Karaf. Кроме того, сфера JAAS может счастливо сосуществовать с другими сферами JAAS. Модуль должен реализовывать интерфейс javax.security.auth.spi.LoginModule :
public class MongoDbLoginModule implements LoginModule { public void initialize(Subject subject, CallbackHandler callbackHandler, MapsharedState, Map options) { ... } public boolean login() throws LoginException { ... } public boolean commit() throws LoginException { ... } public boolean abort() throws LoginException { ... } public boolean logout() throws LoginException { ... } }
- Метод login () аутентифицирует входящий запрос.
- commit () вызывается при успешной аутентификации и обычно устанавливает группы / роли, членом которых является зарегистрированный участник. Членство в группе / роли используется для проверки правил авторизации на более позднем этапе. Например, модули авторизации ActiveMQ полагаются на группы / роли JAAS, которые настраиваются на этом этапе.
- abort () вызывается при сбое аутентификации.
- Выход из системы () вызов предоставляет возможность очистить государственную установку во время входа и совершать фазы.
- initialize () включает настройки, предоставленные в файле конфигурации JAAS (показано позже).
MongoDB
MongoDB взаимодействует напрямую через Java-драйвер MongoDB. Библиотека отображения, такая как Spring Data, Morphia или Jongo, используется не только для того, чтобы избежать дополнительных зависимостей, но и просто потому, что схема слишком тривиальна для использования уровня ODM.
Модуль JAAS ожидает следующие параметры конфигурации:
- Хост и порт, где работает MongoDB.
- База данных и коллекция MongoDB, в которой хранятся данные аутентификации.
- Имя атрибута в коллекции MongoDB, которое сопоставляется с именем пользователя принципала.
- Имя атрибута в коллекции MongoDB, который сопоставляется с паролем принципала.
инициализация
initialize () сохранит вышеуказанные настройки:
public void initialize(Subject subject, CallbackHandler callbackHandler, MapsharedState, Map options) { this.subject = subject; this.handler = callbackHandler; mongoDbHost = getString(options, MONGODB_HOST); mongoDbPort = getInteger(options, MONGODB_PORT); mongoDbDatabase = getString(options, MONGODB_DB); mongoDbCollection = getString(options, MONGODB_COLLECTION); mongoDbAttrUser = getString(options, MONGODB_ATTR_USER); mongoDbAttrPassword = getString(options, MONGODB_ATTR_PASSWORD); }
Аутентификация
login () получит пару имя пользователя / пароль и аутентифицирует учетные данные:
public boolean login() throws LoginException { Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("User name"); callbacks[1] = new PasswordCallback("Password", false); try { handler.handle(callbacks); } catch (IOException ioe) { throw (LoginException) new LoginException().initCause(ioe); } catch (UnsupportedCallbackException uce) { throw (LoginException) new LoginException().initCause(uce); } username = ((NameCallback) callbacks[0]).getName(); if (username == null) return false; String password = ""; if (((PasswordCallback) callbacks[1]).getPassword() != null) password = new String(((PasswordCallback) callbacks[1]).getPassword()); authenticate(username, password); return true; }
Метод аутентификации ищет пользователя и сравнивает предоставленный пароль (в виде простого текста) с паролем, хранящимся в базе данных, который может быть либо простым текстом, закодированным SHA, либо соленым SHA (SSHA).
private void authenticate(String username, String password) throws LoginException { DBObject dbObject = findUser(username); if (dbObject == null) { throw new FailedLoginException(String.format( "Wrong username or password for user %s.", username)); } if (!dbObject.containsField(mongoDbAttrPassword)) { throw new FailedLoginException(String.format( "Wrong username or password for user %s.", username)); } String encPassword = (String) dbObject.get(mongoDbAttrPassword); if (StringUtils.isEmpty(encPassword)) { throw new FailedLoginException(String.format( "Wrong username or password for user %s.", username)); } verifyPassword(encPassword, password); } private void verifyPassword(String encPassword, String rawPassword) throws FailedLoginException { boolean result = getPasswordEncoder().isPasswordValid(encPassword, rawPassword, null); if (result) return; throw new FailedLoginException(String.format( "Wrong username or password for user %s.", username)); }
Кодировщик паролей является урезанной версией кодировщика паролей LDAP Spring Security, который поддерживает как хешированные пароли, так и SHA / SSHA.
Настройка Карафа
Модуль JAAS развертывается в Karaf через обычный контекст проекта:
blueprint.xml
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0" xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0"> <cm:property-placeholder persistent-id="io.modio.blog.security.activemq"/> <jaas:config name="activemq-jaas"> <jaas:module className="io.modio.blog.security.activemq.jaas.mongodb.MongoDbLoginModule" flags="required"> mongodb.host=${io.modio.blog.security.activemq.jaas.mongodb.host} mongodb.port=${io.modio.blog.security.activemq.jaas.mongodb.port} mongodb.db=${io.modio.blog.security.activemq.jaas.mongodb.db} mongodb.collection=${io.modio.blog.security.activemq.jaas.mongodb.collection} mongodb.attribute.user=${io.modio.blog.security.activemq.jaas.mongodb.attribute.user} mongodb.attribute.password=${io.modio.blog.security.activemq.jaas.mongodb.attribute.password} </jaas:module> </jaas:config> </blueprint>
Конфигурация ActiveMQ
ActiveMQ может быть настроен через activemq.xml для использования области JAAS с именем activemq-jaas :
activemq.xml
<beans ...> <broker xmlns="http://activemq.apache.org/schema/core" ... <plugins> <jaasAuthenticationPlugin configuration="activemq-jaas" /> </plugins> ... </broker> </beans>
Внедрение сервисов OSGi в царство JAAS
Описанный выше модуль JAAS на основе MongoDB связывает конкретное внутреннее хранилище с функциями аутентификации. В идеале хранилище аутентификации бэкэнда должно быть абстрагировано от универсального интерфейса управления, такого как:
PrincipalManager.java
public interface PrincipalManager { Principal add(Principal principal) throws AuthException; void deleteByAccessKey(String accessKey) throws AuthException; void addToGroupByAccessKey(String accessKey, String groupKey) throws AuthException; void deleteFromGroupByAccessKey(String accessKey, String groupKey) throws AuthException; Principal findByAccessKey(String accessKey) throws AuthException; boolean authenticate(String accessKey, String secretKey) throws AuthException; }
Тогда модуль JAAS будет написан в терминах PrincipalManager:
PrincipalManager.java
public class MongoDbLoginModule implements LoginModule { private PrincipalManager principalManager; ... }
К счастью, Karaf предоставляет доступ к контексту пакета Blueprint как часть параметров, предоставляемых вызову initialize () модуля JAAS . Таким образом, доступ к сервису OSGi становится возможным:
MongoDbLoginModule.java
privateT getService(BundleContext bundleContext, Class clazz) { ServiceReference ref = bundleContext.getServiceReference(clazz); T service = bundleContext.getService(ref); return service; } public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { BundleContext bundleContext = (BundleContext)options.get(BundleContext.class.getName()); this.subject = subject; this.handler = callbackHandler; principalManager = getService(bundleContext, PrincipalManager.class); }
Включение ссылок на службу PrincipalManager в определение Blueprint обеспечит доступность службы до активации области JAAS.