Une introduction à Java SASL

Haut Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Dans ce didacticiel, nous allons passer en revue les bases de l'authentification simple et de la couche de sécurité (SASL). Nous comprendrons comment Java prend en charge l'adoption de SASL pour sécuriser la communication.

Dans le processus, nous utiliserons une simple communication client et serveur, en la sécurisant avec SASL.

2. Qu'est-ce que SASL ?

SASL est un cadre d'authentification et de sécurité des données dans les protocoles Internet . Il vise à découpler les protocoles Internet des mécanismes d'authentification spécifiques. Nous comprendrons mieux certaines parties de cette définition au fur et à mesure.

Le besoin de sécurité dans la communication est implicite. Essayons de comprendre cela dans le contexte de la communication client et serveur . En général, le client et le serveur échangent des données sur le réseau. Il est impératif que les deux parties puissent se faire confiance et envoyer des données en toute sécurité.

2.1. Où se situe SASL ?

Dans une application, nous pouvons utiliser SMTP pour envoyer des e-mails et utiliser LDAP pour accéder aux services d'annuaire. Mais chacun de ces protocoles peut prendre en charge un autre mécanisme d'authentification, comme Digest-MD5 ou Kerberos.

Et s'il y avait un moyen pour les protocoles d'échanger les mécanismes d'authentification de manière plus déclarative? C'est exactement là que SASL entre en scène. Les protocoles prenant en charge SASL peuvent invariablement prendre en charge n'importe lequel des mécanismes SASL.

Par conséquent, les applications peuvent négocier un mécanisme approprié et adopter celui-ci pour l'authentification et la communication sécurisée.

2.2. Comment fonctionne SASL ?

Maintenant que nous avons vu où SASL s'inscrit dans le schéma global de sécurité, comprenons comment cela fonctionne.

SASL est un cadre de défi-réponse . Ici, le serveur envoie un défi au client et le client envoie une réponse en fonction du défi. Le défi et la réponse sont des tableaux d'octets de longueur arbitraire et, par conséquent, peuvent transporter toutes les données spécifiques au mécanisme.

Cet échange peut se poursuivre pendant plusieurs itérations et se termine finalement lorsque le serveur n'émet aucun autre défi.

De plus, le client et le serveur peuvent négocier une post-authentification de couche de sécurité. Toutes les communications ultérieures peuvent alors exploiter cette couche de sécurité. Cependant, notez que certains des mécanismes peuvent uniquement prendre en charge l'authentification.

Il est important de comprendre ici que SASL ne fournit qu'un cadre pour l'échange de données de défi et de réponse . Il ne mentionne rien sur les données elles-mêmes ni sur la manière dont elles sont échangées. Ces détails sont laissés aux applications qui adoptent l'utilisation de SASL.

3. Prise en charge SASL en Java

Il existe des API en Java qui prennent en charge le développement d'applications côté client et côté serveur avec SASL. L'API ne dépend pas des mécanismes réels eux-mêmes. Les applications utilisant l'API Java SASL peuvent sélectionner un mécanisme en fonction des fonctionnalités de sécurité requises.

3.1. API Java SASL

Les principales interfaces à remarquer, dans le cadre du package «javax.security.sasl», sont SaslServer et SaslClient .

SaslServer représente le mécanisme côté serveur de SASL.

Voyons comment nous pouvons instancier un SaslServer :

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

Nous utilisons la classe d'usine Sasl pour instancier SaslServer. La méthode createSaslServer accepte plusieurs paramètres:

  • mécanisme - le nom enregistré par l'IANA d'un mécanisme pris en charge par SASL
  • protocole - le nom du protocole pour lequel l'authentification est effectuée
  • serverName - le nom d'hôte complet du serveur
  • props - un ensemble de propriétés utilisées pour configurer l'échange d'authentification
  • callbackHandler - un gestionnaire de rappel à utiliser par le mécanisme sélectionné pour obtenir plus d'informations

Sur ce qui précède, seuls les deux premiers sont obligatoires et les autres peuvent être annulés.

SaslClient représente le mécanisme côté client de SASL. Voyons comment pouvons-nous instancier un SaslClient :

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

Ici encore, nous utilisons la classe d'usine Sasl pour instancier notre SaslClient . La liste des paramètres acceptés par createSaslClient est à peu près la même qu'avant.

Cependant, il existe quelques différences subtiles:

  • mécanismes - ici, voici une liste de mécanismes à essayer
  • autorisationId - il s'agit d'une identification dépendant du protocole à utiliser pour l'autorisation

Les autres paramètres ont une signification et un caractère facultatifs similaires.

3.2. Fournisseur de sécurité Java SASL

Sous l'API Java SASL se trouvent les mécanismes réels qui fournissent les fonctionnalités de sécurité. La mise en œuvre de ces mécanismes est assurée par des fournisseurs de sécurité enregistrés auprès de Java Cryptography Architecture (JCA).

Il peut y avoir plusieurs fournisseurs de sécurité enregistrés auprès du JCA. Chacun de ceux - ci peut prendre en charge un ou plusieurs des mécanismes SASL .

Java est livré avec SunSASL en tant que fournisseur de sécurité, qui est enregistré par défaut en tant que fournisseur JCA. Cependant, cela peut être supprimé ou réorganisé avec tout autre fournisseur disponible.

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.

Dans l'ensemble, SASL est un cadre très léger et offre une grande variété de fonctionnalités de sécurité grâce à des mécanismes enfichables. Les applications adoptant SASL ont beaucoup de choix pour implémenter le bon ensemble de fonctionnalités de sécurité, en fonction des besoins.

7. Conclusion

Pour résumer, dans ce tutoriel, nous avons compris les bases du framework SASL, qui assure l'authentification et la communication sécurisée. Nous avons également discuté des API disponibles en Java pour implémenter les côtés client et serveur de SASL.

Nous avons vu comment utiliser un mécanisme de sécurité via un fournisseur JCA. Enfin, nous avons également parlé de l'utilisation de SASL pour travailler avec différents protocoles et applications.

Comme toujours, le code peut être trouvé sur GitHub.

Fond Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS