Pengantar Java SASL

Java Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Dalam tutorial ini, kita akan membahas dasar-dasar Simple Authentication and Security Layer (SASL). Kami akan memahami bagaimana Java mendukung adopsi SASL untuk mengamankan komunikasi.

Dalam prosesnya, kami akan menggunakan komunikasi klien dan server sederhana, mengamankannya dengan SASL.

2. Apakah SASL itu ?

SASL adalah kerangka kerja untuk otentikasi dan keamanan data dalam protokol Internet . Ini bertujuan untuk memisahkan protokol internet dari mekanisme otentikasi tertentu. Kami akan lebih memahami bagian dari definisi ini saat kita melanjutkan.

Kebutuhan akan keamanan dalam komunikasi tersirat. Mari kita coba memahami ini dalam konteks komunikasi klien dan server . Biasanya klien dan server bertukar data melalui jaringan. Kedua belah pihak harus saling percaya dan mengirim data dengan aman.

2.1. Di Mana SASL Cocok?

Dalam sebuah aplikasi, kami dapat menggunakan SMTP untuk mengirim email dan menggunakan LDAP untuk mengakses layanan direktori. Tetapi setiap protokol ini mungkin mendukung mekanisme otentikasi lain, seperti Digest-MD5 atau Kerberos.

Bagaimana jika ada cara bagi protokol untuk menukar mekanisme otentikasi secara lebih deklaratif? Di sinilah tepatnya SASL berperan. Protokol yang mendukung SASL selalu dapat mendukung salah satu mekanisme SASL.

Oleh karena itu, aplikasi dapat menegosiasikan mekanisme yang sesuai dan mengadopsinya untuk otentikasi dan komunikasi yang aman.

2.2. Bagaimana SASL Bekerja?

Sekarang, setelah kita melihat di mana SASL cocok dengan skema keamanan keseluruhan, mari kita pahami cara kerjanya.

SASL adalah kerangka kerja tantangan-respons . Di sini, server mengeluarkan tantangan kepada klien, dan klien mengirimkan respons berdasarkan tantangan tersebut. Tantangan dan responsnya adalah array byte dengan panjang arbitrer dan, karenanya, dapat membawa data spesifik mekanisme apa pun.

Pertukaran ini dapat berlanjut untuk beberapa iterasi dan akhirnya berakhir ketika server tidak mengeluarkan tantangan lebih lanjut.

Selanjutnya, klien dan server dapat menegosiasikan lapisan keamanan pasca-otentikasi. Semua komunikasi selanjutnya dapat memanfaatkan lapisan keamanan ini. Namun, perhatikan bahwa beberapa mekanisme mungkin hanya mendukung otentikasi.

Penting untuk dipahami di sini bahwa SASL hanya menyediakan kerangka kerja untuk pertukaran data tantangan dan respons . Itu tidak menyebutkan apapun tentang data itu sendiri atau bagaimana mereka dipertukarkan. Detail tersebut ditinggalkan untuk aplikasi yang mengadopsi menggunakan SASL.

3. Dukungan SASL di Java

Ada API di Java yang mendukung pengembangan aplikasi sisi klien dan sisi server dengan SASL. API tidak bergantung pada mekanisme sebenarnya itu sendiri. Aplikasi yang menggunakan Java SASL API dapat memilih mekanisme berdasarkan fitur keamanan yang diperlukan.

3.1. Java SASL API

Antarmuka kunci yang perlu diperhatikan, sebagai bagian dari paket "javax.security.sasl", adalah SaslServer dan SaslClient .

SaslServer mewakili mekanisme sisi server SASL.

Mari kita lihat bagaimana kita dapat membuat contoh SaslServer :

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

Kami menggunakan Sasl kelas pabrik untuk membuat instance SaslServer. Metode createSaslServer menerima beberapa parameter:

  • mekanisme - nama terdaftar IANA dari mekanisme yang didukung SASL
  • protokol - nama protokol yang otentikasi dilakukan
  • serverName - nama host server yang memenuhi syarat
  • props - sekumpulan properti yang digunakan untuk mengkonfigurasi pertukaran otentikasi
  • callbackHandler - penangan panggilan balik yang akan digunakan oleh mekanisme yang dipilih untuk mendapatkan informasi lebih lanjut

Dari yang di atas, hanya dua yang pertama yang wajib diisi, dan sisanya tidak dapat dibatalkan.

SaslClient mewakili mekanisme sisi klien dari SASL. Mari kita lihat bagaimana kita bisa membuat contoh SaslClient :

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

Di sini sekali lagi, kami menggunakan Sasl kelas pabrik untuk membuat instance SaslClient kami . Daftar parameter yang diterima createSaslClient hampir sama seperti sebelumnya.

Namun, ada beberapa perbedaan halus:

  • mekanisme - di sini, ini adalah daftar mekanisme untuk dicoba
  • authorizationId - ini adalah identifikasi yang bergantung pada protokol yang akan digunakan untuk otorisasi

Parameter lainnya serupa dalam arti dan opsionalitasnya.

3.2. Penyedia Keamanan SASL Java

Di bawah Java SASL API adalah mekanisme sebenarnya yang menyediakan fitur keamanan. The pelaksanaan mekanisme ini disediakan oleh penyedia keamanan yang terdaftar dengan Cryptography Architecture Java (JCA).

Mungkin ada beberapa penyedia keamanan yang terdaftar di JCA. Masing-masing dapat mendukung satu atau lebih mekanisme SASL .

Java dikirimkan bersama SunSASL sebagai penyedia keamanan, yang terdaftar sebagai penyedia JCA secara default. Namun, ini dapat dihapus atau disusun ulang dengan penyedia lain yang tersedia.

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.

Secara keseluruhan, SASL adalah kerangka kerja yang sangat ringan dan menawarkan berbagai fitur keamanan melalui mekanisme yang dapat dicolokkan. Aplikasi yang mengadopsi SASL memiliki banyak pilihan dalam mengimplementasikan sekumpulan fitur keamanan yang tepat, tergantung pada kebutuhan.

7. Kesimpulan

Singkatnya, dalam tutorial ini, kami memahami dasar-dasar framework SASL, yang menyediakan otentikasi dan komunikasi yang aman. Kami juga membahas API yang tersedia di Java untuk mengimplementasikan sisi klien dan server SASL.

Kami melihat cara menggunakan mekanisme keamanan melalui penyedia JCA. Terakhir, kami juga berbicara tentang penggunaan SASL dalam bekerja dengan berbagai protokol dan aplikasi.

Seperti biasa, kode dapat ditemukan di GitHub.

Jawa bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya