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はチャレンジアンドレスポンスデータの交換のためのフレームワークのみを提供するということです。 データ自体やデータの交換方法については何も言及されていません。 これらの詳細は、SASLの使用を採用するアプリケーションに残されています。

3. JavaでのSASLサポート

Javaには、SASLを使用したクライアント側とサーバー側の両方のアプリケーションの開発をサポートするAPIがあります。 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 いくつかのパラメータを受け入れます:

  • メカニズム–SASLでサポートされるメカニズムのIANA登録名
  • protocol –認証が行われているプロトコルの名前
  • serverName –サーバーの完全修飾ホスト名
  • props –認証交換の構成に使用される一連のプロパティ
  • callbackHandler –選択したメカニズムが詳細情報を取得するために使用するコールバックハンドラー

上記のうち、最初の2つだけが必須であり、残りはnull許容です。

SaslClient は、SASLのクライアント側メカニズムを表します。 SaslClientをインスタンス化する方法を見てみましょう。

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

ここでも、ファクトリクラス Sasl を使用して、SaslClientをインスタンス化しています。 createSaslClient が受け入れるパラメーターのリストは、以前とほとんど同じです。

ただし、いくつかの微妙な違いがあります。

  • メカニズム–ここで、これは試してみるメカニズムのリストです
  • authenticationId –これは承認に使用されるプロトコル依存のIDです

残りのパラメーターは、意味とオプション性が似ています。

3.2. JavaSASLセキュリティプロバイダー

Java SASL APIの下には、セキュリティ機能を提供する実際のメカニズムがあります。 これらのメカニズムの実装は、 Java Cryptography Architecture (JCA)に登録されているセキュリティプロバイダーによって提供されます。

JCAに登録されているセキュリティプロバイダーは複数存在する可能性があります。 これらの各は、1つ以上のSASLメカニズムをサポートする場合があります。

Javaには、セキュリティプロバイダーとしてSunSASLが付属しており、デフォルトでJCAプロバイダーとして登録されます。 ただし、これは削除されるか、他の利用可能なプロバイダーに再注文される場合があります。

さらに、カスタムセキュリティプロバイダーを提供することは常に可能です。 これには、インターフェースSaslClientおよびSaslServerを実装する必要があります。 そうすることで、カスタムセキュリティメカニズムも実装できます。

4. 例によるSASL

SaslServerSaslClientの作成方法を確認したので、次にそれらの使用方法を理解します。 クライアントとサーバーのコンポーネントを開発します。 これらは、認証を達成するためにチャレンジとレスポンスを繰り返し交換します。 ここでは、簡単な例でDIGEST-MD5メカニズムを利用します。

4.1. クライアントとサーバーCallbackHandler

前に見たように、CallbackHandlerの実装をSaslServerおよびSaslClientに提供する必要があります。 現在、 CallbackHandler は、handleという単一のメソッドを定義する単純なインターフェースです。 このメソッドは、コールバックの配列を受け入れます。

ここで、コールバックは、セキュリティメカニズムが呼び出し元のアプリケーションから認証データを収集する方法を示します。 たとえば、セキュリティメカニズムでは、ユーザー名とパスワードが必要になる場合があります。 NameCallbackPasswordCallbackのようなかなりの数のCallback実装が使用可能です。

まず、サーバーの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を作成しました。 また、DIGEST-MD5メカニズム用にSaslClientおよびSaslServerをインスタンス化しました。

今こそ、それらが実際に動作しているのを見るときです。

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

ここで何が起こっているのかを理解してみましょう。

  • まず、クライアントはサーバーからデフォルトのチャレンジを取得します
  • 次に、クライアントはチャレンジを評価し、応答を準備します
  • このチャレンジ/レスポンス交換は、もう1サイクル続きます
  • このプロセスでは、クライアントとサーバーはコールバックハンドラーを使用して、メカニズムの必要に応じて追加のデータを収集します。
  • これで認証はここで終わりますが、実際には、複数のサイクルにわたって繰り返すことができます

チャレンジおよびレスポンスバイト配列の一般的な交換は、ネットワークを介して行われます。 ただし、ここでは簡単にするために、ローカル通信を想定しています。

4.3. SASLセキュア通信

前述したように、SASLは、認証だけでなく安全な通信をサポートできるフレームワークです。 ただし、これは、基盤となるメカニズムがサポートしている場合にのみ可能です

まず、安全な通信をネゴシエートできたかどうかを最初に確認しましょう。

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

ここで、QOPは保護の品質を表します。 これは、クライアントとサーバーが認証中にネゴシエートするものです。 「auth-int」の値は、認証と整合性を示します。 一方、「auth-conf」の値は、認証、整合性、および機密性を示します。

セキュリティレイヤーができたら、それを活用して通信を保護できます。

クライアントで発信通信を保護する方法を見てみましょう。

byte[] outgoing = "Baeldung".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("Baeldung", new String(incoming, StandardCharsets.UTF_8));

5. 実世界のSASL

これで、SASLとは何か、JavaでSASLを使用する方法を正しく理解できました。 しかし、通常、少なくとも日常業務では、SASLを使用することになるわけではありません。

前に見たように、SASLは主にLDAPやSMTPなどのプロトコルを対象としています。 ただし、ますます多くのアプリケーションがSASLに組み込まれています。たとえば、Kafkaです。 では、SASLを使用してそのようなサービスで認証するにはどうすればよいでしょうか。

選択したメカニズムとしてPLAINを使用してKafkaBrokerforSASLを構成したとします。 PLAINは、プレーンテキストのユーザー名とパスワードの組み合わせを使用して認証することを意味します。

ここで、SASL/PLAINを使用してKafkaブローカーに対して認証するようにJavaクライアントを構成する方法を見てみましょう。

まず、簡単なJAAS構成「kafka_jaas.conf」を提供します。

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

JVMの起動時に、このJAAS構成を利用します。

-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用のSecureSockets Layer(SSL)を実装するJavaのパッケージのセットです。 データの暗号化、クライアントとサーバーの認証、およびメッセージの整合性を提供します。 SASLとは異なり、JSSEは公開鍵インフラストラクチャ(PKI)に依存して機能します。 したがって、SASLはJSSEよりも柔軟で軽量であることがわかります。
  • Java GSS API (JGSS): JGGSは、汎用セキュリティサービスアプリケーションプログラミングインターフェイス(GSS-API)のJava言語バインディングです。 GSS-APIは、アプリケーションがセキュリティサービスにアクセスするためのIETF標準です。 Javaでは、GSS-APIの下で、Kerberosがサポートされている唯一のメカニズムです。 Kerberosも、動作するためにKerberos化されたインフラストラクチャを必要とします。 SASLと比較すると、ここではまだ選択肢が限られており、重量があります。

全体として、SASLは非常に軽量なフレームワークであり、プラグ可能なメカニズムを通じてさまざまなセキュリティ機能を提供します。 SASLを採用するアプリケーションには、必要に応じて、適切なセキュリティ機能のセットを実装するための多くの選択肢があります。

7. 結論

要約すると、このチュートリアルでは、認証と安全な通信を提供するSASLフレームワークの基本を理解しました。 また、SASLのクライアント側とサーバー側を実装するためにJavaで使用できるAPIについても説明しました。

JCAプロバイダーを介してセキュリティメカニズムを使用する方法を見ました。 最後に、さまざまなプロトコルやアプリケーションでの作業におけるSASLの使用法についても説明しました。

いつものように、コードはGitHubにあります。