«1. Обзор

В этом руководстве мы рассмотрим основы простой аутентификации и уровня безопасности (SASL). Мы поймем, как Java поддерживает использование SASL для защиты связи.

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

2. Что такое SASL?

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

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

2.1. Где вписывается SASL?

В приложении мы можем использовать SMTP для отправки электронной почты и использовать LDAP для доступа к службам каталогов. Но каждый из этих протоколов может поддерживать другой механизм аутентификации, например Digest-MD5 или Kerberos.

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

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

2.2. Как работает SASL?

Теперь, когда мы увидели место SASL в общей схеме безопасности, давайте разберемся, как это работает.

SASL — это фреймворк типа «вызов-ответ». Здесь сервер выдает вызов клиенту, а клиент отправляет ответ на основе вызова. Запрос и ответ представляют собой байтовые массивы произвольной длины и, следовательно, могут нести любые данные, специфичные для механизма.

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

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

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

3. Поддержка SASL в Java

В Java есть API, поддерживающие разработку как клиентских, так и серверных приложений с использованием SASL. API не зависит от самих реальных механизмов. Приложения, использующие Java SASL API, могут выбирать механизм на основе требуемых функций безопасности.

3.1. Java SASL API

Ключевыми интерфейсами, на которые следует обратить внимание в составе пакета «javax.security.sasl», являются SaslServer и SaslClient.

SaslServer представляет серверный механизм SASL.

Давайте посмотрим, как мы можем создать экземпляр SaslServer:

SaslServer ss = Sasl.createSaslServer(
  mechanism, 
  protocol, 
  serverName, 
  props, 
  callbackHandler);

Мы используем фабричный класс Sasl для создания экземпляра SaslServer. Метод createSaslServer принимает несколько параметров:

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

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

SaslClient представляет клиентский механизм SASL. Давайте посмотрим, как мы можем создать экземпляр SaslClient:

SaslClient sc = Sasl.createSaslClient(
  mechanisms, 
  authorizationId, 
  protocol, 
  serverName, 
  props,
  callbackHandler);

Здесь мы снова используем фабричный класс Sasl для создания экземпляра нашего SaslClient. Список параметров, которые принимает createSaslClient, почти такой же, как и раньше.

Однако есть небольшие отличия:

    «механизмы — здесь это список механизмов, которые можно попробовать из authorId — это зависящая от протокола идентификация, которая будет использоваться для авторизации

Остальные параметры аналогичны по значению и являются необязательными.

3.2. Java SASL Security Provider

В основе Java SASL API лежат фактические механизмы, обеспечивающие функции безопасности. Реализация этих механизмов обеспечивается поставщиками безопасности, зарегистрированными в архитектуре криптографии Java (JCA).

В JCA может быть зарегистрировано несколько поставщиков безопасности. Каждый из них может поддерживать один или несколько механизмов SASL.

Java поставляется с SunSASL в качестве поставщика безопасности, который по умолчанию регистрируется как поставщик JCA. Тем не менее, это может быть удалено или изменено с любыми другими доступными поставщиками.

Более того, всегда можно указать собственного поставщика безопасности. Это потребует от нас реализации интерфейсов SaslClient и SaslServer. При этом мы также можем реализовать наш собственный механизм безопасности!

4. SASL на примере

Теперь, когда мы увидели, как создавать SaslServer и SaslClient, пришло время понять, как их использовать. Мы будем разрабатывать клиентские и серверные компоненты. Они будут итеративно обмениваться вызовами и ответами для достижения аутентификации. Мы будем использовать механизм DIGEST-MD5 в нашем простом примере.

4.1. Клиент и сервер CallbackHandler

Как мы видели ранее, нам нужно предоставить реализации CallbackHandler для SaslServer и SaslClient. Теперь CallbackHandler — это простой интерфейс, определяющий единственный метод — handle. Этот метод принимает массив обратного вызова.

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

Давайте посмотрим, как мы можем определить CallbackHandler для сервера, для начала:

public class ServerCallbackHandler implements CallbackHandler {
    @Override
    public void handle(Callback[] cbs) throws IOException, UnsupportedCallbackException {
        for (Callback cb : cbs) {
            if (cb instanceof AuthorizeCallback) {
                AuthorizeCallback ac = (AuthorizeCallback) cb;
                //Perform application-specific authorization action
                ac.setAuthorized(true);
            } else if (cb instanceof NameCallback) {
                NameCallback nc = (NameCallback) cb;
                //Collect username in application-specific manner
                nc.setName("username");
            } else if (cb instanceof PasswordCallback) {
                PasswordCallback pc = (PasswordCallback) cb;
                //Collect password in application-specific manner
                pc.setPassword("password".toCharArray());
            } else if (cb instanceof RealmCallback) { 
                RealmCallback rc = (RealmCallback) cb; 
                //Collect realm data in application-specific manner 
                rc.setText("myServer"); 
            }
        }
    }
}

Теперь давайте посмотрим на нашу клиентскую часть Callbackhandler:

public class ClientCallbackHandler implements CallbackHandler {
    @Override
    public void handle(Callback[] cbs) throws IOException, UnsupportedCallbackException {
        for (Callback cb : cbs) {
            if (cb instanceof NameCallback) {
                NameCallback nc = (NameCallback) cb;
                //Collect username in application-specific manner
                nc.setName("username");
            } else if (cb instanceof PasswordCallback) {
                PasswordCallback pc = (PasswordCallback) cb;
                //Collect password in application-specific manner
                pc.setPassword("password".toCharArray());
            } else if (cb instanceof RealmCallback) { 
                RealmCallback rc = (RealmCallback) cb; 
                //Collect realm data in application-specific manner 
                rc.setText("myServer"); 
            }
        }
    }
}

Чтобы уточнить, мы зацикливаемся через массив обратного вызова и обрабатывая только определенные. Те, которые нам нужно обработать, специфичны для используемого механизма, которым здесь является DIGEST-MD5.

4.2. Аутентификация SASL

Итак, мы написали клиентский и серверный CallbackHandler. Мы также создали экземпляры SaslClient и SaslServer для механизма DIGEST-MD5.

Теперь пришло время увидеть их в действии:

@Test
public void givenHandlers_whenStarted_thenAutenticationWorks() throws SaslException {
    byte[] challenge;
    byte[] response;
 
    challenge = saslServer.evaluateResponse(new byte[0]);
    response = saslClient.evaluateChallenge(challenge);
 
    challenge = saslServer.evaluateResponse(response);
    response = saslClient.evaluateChallenge(challenge);
 
    assertTrue(saslServer.isComplete());
    assertTrue(saslClient.isComplete());
}

Давайте попробуем понять, что здесь происходит:

    Сначала наш клиент получает вызов по умолчанию от сервера Затем клиент оценивает вызов и готовится ответ Этот обмен запрос-ответ продолжается еще один цикл. В процессе клиент и сервер используют обработчики обратного вызова для сбора любых дополнительных данных, необходимых механизму. циклы

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

4.3. Безопасная связь SASL

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

Во-первых, давайте сначала проверим, смогли ли мы договориться о безопасном соединении:

String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
 
assertEquals("auth-conf", qop);

Здесь QOP означает качество защиты. Это то, о чем клиент и сервер договариваются во время аутентификации. Значение «auth-int» указывает на аутентификацию и целостность. В то время как значение «auth-conf» указывает на аутентификацию, целостность и конфиденциальность.

Когда у нас есть уровень безопасности, мы можем использовать его для защиты нашего общения.

Давайте посмотрим, как мы можем защитить исходящие сообщения в клиенте:

byte[] outgoing = "Baeldung".getBytes();
byte[] secureOutgoing = saslClient.wrap(outgoing, 0, outgoing.length);
 
// Send secureOutgoing to the server over the network

И, аналогично, сервер может обрабатывать входящие сообщения:

// Receive secureIncoming from the client over the network
byte[] incoming = saslServer.unwrap(secureIncoming, 0, netIn.length);
 
assertEquals("Baeldung", new String(incoming, StandardCharsets.UTF_8));

5. SASL в реальном мире

«Итак, теперь у нас есть четкое представление о том, что такое SASL и как его использовать в Java. Но, как правило, мы используем SASL не для этого, по крайней мере, в нашей повседневной жизни.

Как мы видели ранее, SASL в первую очередь предназначен для таких протоколов, как LDAP и SMTP. Хотя с SASL появляется все больше и больше приложений — например, Kafka. Итак, как мы используем SASL для аутентификации в таких службах?

Предположим, мы настроили Kafka Broker для SASL с PLAIN в качестве механизма выбора. PLAIN просто означает, что он аутентифицируется с использованием комбинации имени пользователя и пароля в виде простого текста.

Давайте теперь посмотрим, как мы можем настроить Java-клиент для использования SASL/PLAIN для аутентификации в Kafka Broker.

Мы начинаем с предоставления простой конфигурации JAAS, «kafka_jaas.conf»:

KafkaClient {
  org.apache.kafka.common.security.plain.PlainLoginModule required
  username="username"
  password="password";
};

Мы используем эту конфигурацию JAAS при запуске JVM:

-Djava.security.auth.login.config=kafka_jaas.conf

Наконец, мы должны добавить несколько свойств для передачи нашим экземплярам производителя и потребителя:

security.protocol=SASL_SSL
sasl.mechanism=PLAIN

Это все, что нужно сделать. Однако это лишь малая часть клиентских конфигураций Kafka. Помимо PLAIN, Kafka также поддерживает GSSAPI/Kerberos для аутентификации.

6. SASL в сравнении

Хотя SASL довольно эффективен в обеспечении независимого от механизма способа аутентификации и защиты связи между клиентом и сервером. Однако SASL — не единственное решение, доступное в этом отношении.

Сама Java предоставляет другие механизмы для достижения этой цели. Мы кратко обсудим их и поймем, как они работают против SASL:

    Java Secure Socket Extension (JSSE): JSSE — это набор пакетов в Java, который реализует Secure Sockets Layer (SSL) для Java. Он обеспечивает шифрование данных, аутентификацию клиента и сервера и целостность сообщений. В отличие от SASL, JSSE для работы использует инфраструктуру открытых ключей (PKI). Таким образом, SASL оказывается более гибким и легким, чем JSSE. Java GSS API (JGSS): JGGS — это привязка языка Java для универсального интерфейса прикладного программирования службы безопасности (GSS-API). GSS-API — это стандарт IETF для приложений, обеспечивающих доступ к службам безопасности. В Java, в рамках GSS-API, Kerberos является единственным поддерживаемым механизмом. Для работы Kerberos снова требуется инфраструктура Kerberized. По сравнению с SASL, здесь выбор ограничен и тяжеловесен.

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

7. Заключение

Подводя итог, в этом руководстве мы поняли основы структуры SASL, которая обеспечивает аутентификацию и безопасную связь. Мы также обсудили доступные в Java API для реализации SASL на стороне клиента и сервера.

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

Как всегда, код можно найти на GitHub.