Перейти к основному содержимому

Введение в Java SASL

· 10 мин. чтения

1. Обзор

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

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

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

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

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

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

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

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

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

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

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

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

./176e6839d2fd8062fd72175756e39312.jpg

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

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

Здесь важно понимать, что 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.
  • протокол — имя протокола, для которого выполняется аутентификация
  • serverName — полное имя хоста сервера
  • props — набор свойств, используемых для настройки обмена аутентификацией.
  • callbackHandler — обработчик обратного вызова, который будет использоваться выбранным механизмом для получения дополнительной информации.

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

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

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

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

Тем не менее, есть некоторые тонкие различия:

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

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

3.2. Поставщик безопасности Java SASL

В основе 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 . Этот метод принимает массив Callback .

Здесь обратный вызов представляет собой способ для механизма безопасности собирать данные аутентификации из вызывающего приложения . Например, механизм безопасности может потребовать имя пользователя и пароль. Существует довольно много реализаций обратного вызова , таких как 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 = "ForEach".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("ForEach", 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 .