1. Overview
In this tutorial, we’ll go through the basics of Simple Authentication and Security Layer (SASL). We’ll understand how Java supports adopting SASL for securing communication.
In the process, we’ll use simple client and server communication, securing it with SASL.
2. What Is SASL?
SASL is a framework for authentication and data security in Internet protocols. It aims to decouple internet protocols from specific authentication mechanisms. We’ll better understand parts of this definition as we go along.
The need for security in communication is implicit. Let’s try to understand this in the context of client and server communication. Typically client and server exchange data over the network. It is imperative that both parties can trust each other and send data securely.
2.1. Where Does SASL Fit In?
In an application, we may use SMTP to send emails and use LDAP to access directory services. But each of these protocols may support another authentication mechanism, like Digest-MD5 or Kerberos.
What if there was a way for protocols to swap authentication mechanisms more declaratively? This is exactly where SASL comes into the picture. Protocols supporting SASL can invariably support any of the SASL mechanisms.
Hence, applications can negotiate a suitable mechanism and adopt that for authentication and secure communication.
2.2. How Does SASL Work?
Now, that we’ve seen where SASL fits in the overall scheme of security, let’s understand how it works.
SASL is a challenge-response framework. Here, the server issues a challenge to the client, and the client sends a response based on the challenge. The challenge and response are byte arrays of arbitrary length and, hence, can carry any mechanism-specific data.
This exchange can continue for multiple iterations and finally ends when the server issues no further challenge.
Furthermore, the client and server can negotiate a security layer post-authentication. All subsequent communication can then leverage this security layer. However, note that some of the mechanisms may only support authentication.
It’s important to understand here that SASL only provides a framework for the exchange of challenge and response data. It does not mention anything about the data itself or how they are exchanged. Those details are left for the applications adopting to use SASL.
3. SASL Support in Java
There are APIs in Java that support developing both client-side and server-side applications with SASL. The API is not dependent on the actual mechanisms themselves. Applications using Java SASL API can select a mechanism based on security features required.
3.1. Java SASL API
The key interfaces to notice, as part of the package “javax.security.sasl”, are SaslServer and SaslClient.
SaslServer represents the server-side mechanism of SASL.
Let’s see how we can instantiate a SaslServer:
SaslServer ss = Sasl.createSaslServer(
mechanism,
protocol,
serverName,
props,
callbackHandler);
We are using the factory class Sasl to instantiate SaslServer. The method createSaslServer accepts several parameters:
- mechanism – the IANA registered name of a SASL supported mechanism
- protocol – the name of the protocol for which authentication is being done
- serverName – the fully qualified hostname of the server
- props – a set of properties used to configure the authentication exchange
- callbackHandler – a callback handler to be used by the selected mechanism to get further information
Out of the above, only the first two are mandatory, and the rest are nullable.
SaslClient represents the client-side mechanism of SASL. Let’s see how can we instantiate a SaslClient:
SaslClient sc = Sasl.createSaslClient(
mechanisms,
authorizationId,
protocol,
serverName,
props,
callbackHandler);
Here again, we’re using the factory class Sasl to instantiate our SaslClient. The list of parameters which createSaslClient accepts is pretty much the same as before.
However, there are some subtle differences:
- mechanisms – here, this is a list of mechanisms to try from
- authorizationId – this is a protocol-dependent identification to be used for authorization
The rest of the parameters are similar in meaning and their optionality.
3.2. Java SASL Security Provider
Beneath the Java SASL API are the actual mechanisms that provide the security features. The implementation of these mechanisms is provided by security providers registered with the Java Cryptography Architecture (JCA).
There can be multiple security providers registered with the JCA. Each of these may support one or more of the SASL mechanisms.
Java ships with SunSASL as a security provider, which gets registered as a JCA provider by default. However, this may be removed or reordered with any other available providers.
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.
Overall, SASL is a very lightweight framework and offers a wide variety of security features through pluggable mechanisms. Applications adopting SASL have a lot of choices in implementing the right set of security features, depending upon the need.
7. Conclusion
To sum up, in this tutorial, we understood the basics of the SASL framework, which provides authentication and secure communication. We also discussed the APIs available in Java for implementing the client- and server-side of SASL.
We saw how to use a security mechanism through a JCA provider. Finally, we also talked about the usage of SASL in working with different protocols and applications.
As always, the code can be found over on GitHub.