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,
Map
sharedState, 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,
Map
sharedState, 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.