Статьи

Плагин JAAS на основе MongoDB для Karaf и ActiveMQ

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

private 
 
   T 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.