Въведение в Java SASL

Java Top

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

В този урок ще разгледаме основите на Simple Authentication and Security Layer (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. API на Java SASL

Ключовите интерфейси, които трябва да забележите, като част от пакета „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 приема е почти същият като преди.

Има обаче някои фини разлики:

  • механизми - ето, това е списък на механизмите, от които да опитате
  • authId - това е протокол-зависима идентификация, която да се използва за оторизация

Останалите параметри са сходни по значение и по избор.

3.2. Доставчик на сигурност на Java SASL

Под Java SASL API са действителните механизми, които осигуряват защитните функции. В изпълнение на тези механизми е предоставена от институциите за сигурност , регистрирани в Java Криптография Architecture (JCA).

В JCA могат да бъдат регистрирани множество доставчици на сигурност. Всеки от тях може да поддържа един или повече от механизмите на SASL .

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

Moreover, it is always possible to provide a custom security provider. This will require us to implement the interfaces SaslClient and SaslServer. In doing so, we may implement our custom security mechanism as well!

4. SASL Through an Example

Now that we've seen how to create a SaslServer and a SaslClient, it's time to understand how to use them. We'll be developing client and server components. These will exchange challenge and response iteratively to achieve authentication. We'll make use of the DIGEST-MD5 mechanism in our simple example here.

4.1. Client and Server CallbackHandler

As we saw earlier, we need to provide implementations of CallbackHandler to SaslServer and SaslClient. Now, CallbackHandler is a simple interface that defines a single method — handle. This method accepts an array of Callback.

Here, Callback presents a way for the security mechanism to collect authentication data from the calling application. For instance, a security mechanism may require a username and password. There are quite a few Callback implementations like NameCallback and PasswordCallback available for use.

Let's see how we can define a CallbackHandler for the server, to begin with:

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"); } } } }

Now, let's see our client-side of the 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"); } } } }

To clarify, we're looping through the Callback array and handling only specific ones. The ones that we have to handle is specific to the mechanism in use, which is DIGEST-MD5 here.

4.2. SASL Authentication

So, we've written our client and server CallbackHandler. We've also instantiated SaslClient and SaslServer for DIGEST-MD5 mechanism.

Now is the time to see them in action:

@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()); }

Let's try to understand what is happening here:

  • First, our client gets the default challenge from the server
  • The client then evaluates the challenge and prepares a response
  • This challenge-response exchange continues for one more cycle
  • In the process, the client and server make use of callback handlers to collect any additional data as needed by the mechanism
  • This concludes our authentication here, but in reality, it can iterate over multiple cycles

A typical exchange of challenge and response byte arrays happens over the network. But, here for simplicity, we've assumed local communication.

4.3. SASL Secure Communication

As we discussed earlier, SASL is a framework capable of supporting secure communication beyond just authentication. However, this is only possible if the underlying mechanism supports it.

Firstly, let's first check if we have been able to negotiate a secure communication:

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

Here, QOP stands for the quality of protection. This is something that the client and server negotiate during authentication. A value of “auth-int” indicates authentication and integrity. While, a value of “auth-conf” indicates authentication, integrity, and confidentiality.

Once we have a security layer, we can leverage that to secure our communication.

Let's see how we can secure outgoing communication in the client:

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

And, similarly, the server can process incoming communication:

// 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 in the Real World

So, we now have a fair understanding of what SASL is and how to use it in Java. But, typically, that's not what we'll end up using SASL for, at least in our daily routine.

As we saw earlier, SASL is primarily meant for protocols like LDAP and SMTP. Although, more and more applications and coming on board with SASL — for instance, Kafka. So, how do we use SASL to authenticate with such services?

Let's suppose we've configured Kafka Broker for SASL with PLAIN as the mechanism of choice. PLAIN simply means that it authenticates using a combination of username and password in plain text.

Let's now see how can we configure a Java client to use SASL/PLAIN to authenticate against the Kafka Broker.

We begin by providing a simple JAAS configuration, “kafka_jaas.conf”:

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

We make use of this JAAS configuration while starting the JVM:

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

Finally, we have to add a few properties to pass to our producer and consumer instances:

security.protocol=SASL_SSL sasl.mechanism=PLAIN

That's all there is to it. This is just a small part of Kafka client configurations, though. Apart from PLAIN, Kafka also supports GSSAPI/Kerberos for authentication.

6. SASL in Comparision

Although SASL is quite effective in providing a mechanism-neutral way of authenticating and securing client and server communication. However, SASL is not the only solution available in this regard.

Java itself provides other mechanisms to achieve this objective. We'll briefly discuss them and understand how they fare against SASL:

  • Java Secure Socket Extension (JSSE): JSSE is a set of packages in Java that implements Secure Sockets Layer (SSL) for Java. It provides data encryption, client and server authentication, and message integrity. Unlike SASL, JSSE relies on a Public Key Infrastructure (PKI) to work. Hence, SASL works out to be more flexible and lightweight than JSSE.
  • Java GSS API (JGSS): JGGS is the Java language binding for Generic Security Service Application Programming Interface (GSS-API). GSS-API is an IETF standard for applications to access security services. In Java, under GSS-API, Kerberos is the only mechanism supported. Kerberos again requires a Kerberised infrastructure to work. Compared to SASL, here yet, choices are limited and heavyweight.

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

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

В обобщение, в този урок разбрахме основите на рамката SASL, която осигурява удостоверяване и сигурна комуникация. Също така обсъдихме API-тата, налични в Java за внедряване на клиентската и сървърната част на SASL.

Видяхме как да използваме механизъм за сигурност чрез JCA доставчик. Накрая говорихме и за използването на SASL при работа с различни протоколи и приложения.

Както винаги, кодът може да бъде намерен в GitHub.

Дъно на Java

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА