CData JDBC ドライバーの ITokenStore インターフェースの実装



最新のアプリケーションでは、外部サービスとの接続に OAuth ベースの認証が広く使われています。しかし、分散環境でのアクセストークン管理には多くの課題があります。ローカルファイルやメモリにトークンを保存する従来の方法はデスクトップアプリケーションでは有効ですが、クラウドベースやマルチテナント環境では、トークンの喪失や上書き、意図しない共有が発生し、認証エラーやセキュリティリスクにつながる恐れがあります。

CData ドライバーおよびコネクタは、ITokenStore インターフェースを通じてこの課題を解決します。このインターフェースにより、トークンの保存とトークン管理を分離できます。開発者はカスタムストレージソリューションを実装するだけで、CData コネクタが OAuth ワークフロー、トークンのリフレッシュ、暗号化を自動的に処理します。これにより、トークンは安全に保たれ、ユーザー固有の状態が維持され、インスタンス間でアクセス可能になるため、スケーラブルなエンタープライズレベルのアプリケーションを構築できます。

環境のセットアップとインストール

Java JDK のインストール

Java JDK バージョン 8 以降をインストールします(JDK 11 または 17 を推奨)。ターミナルで java --version を実行して、サポートされているバージョンがインストールされていることを確認してください。

CData JDBC ドライバーのインストール

  • 必要なデータソース用の CData JDBC ドライバーをダウンロードしてインストールします
  • ダウンロード後、JDBC の .jar ファイルが lib ディレクトリからアクセスできることを確認してください

Note: この記事では、Google Sheets をデータソースの例として使用します

Java 開発環境のセットアップ

VS Code、IntelliJ IDEA、Eclipse など、お好みの Java IDE を使用してください。 新しい Java プロジェクトを作成し、以下の構造で整理します:

TokenStoreDemo/
 ├── lib/
 │    └── cdata.jdbc.googlesheets.jar
 └── src/
      └── main/
           ├── TokenStore.java
           ├── GoogleSheetsTokenStore.java
           └── GoogleSheetsTokenStoreFactory.java

CData JDBC ドライバーの JAR ファイルを lib フォルダに配置し、プロジェクトのクラスパスに追加してください。 次に、データソース用の OAuth 資格情報を準備します。Google Sheets の場合は、Client IDClient Secret、および Redirect URI(http://localhost:33333 に設定)を作成する必要があります。これらの値は OAuth 認証フローで必要になります。

カスタム ITokenStore の実装

カスタムトークンストアの実装は、OAuth トークン管理においてそれぞれ特定の役割を持つ 3 つの Java クラスで構成されます。

ステップ 1: トークン保存ロジックの実装

このクラスは ITokenStore インターフェースを実装し、トークンの保存と取得の方法を定義します。この例では、トークンをローカルファイルシステムに保存し、シンプルなファイルベースのロック機構で同時書き込みを防止しています。

package main;

import cdata.jdbc.googlesheets.ITokenStore;

import java.nio.file.Files;
import java.nio.file.Path;

public class GoogleSheetsTokenStore implements ITokenStore {

    private final Path tokenFile;
    private final Path lockFile;

    public GoogleSheetsTokenStore(String context) throws Exception {

        // Strip externalToken:// prefix
        if (context.startsWith("externalToken://")) {
            context = context.substring("externalToken://".length());
        }

        // Expecting something like user=testuser
        String user = context.split("=")[1];

        tokenFile = Path.of("token_" + user + ".txt");
        lockFile  = Path.of("token_" + user + ".lock");
    }

    @Override
    public String readToken(String tokenName) throws Exception {
        System.out.println("readToken called");

        if (Files.exists(tokenFile)) {
            return Files.readString(tokenFile);
        }
        return null;
    }

    @Override
    public void writeToken(String tokenName, String token) throws Exception {
        System.out.println("writeToken called");
        Files.writeString(tokenFile, token);
    }

    @Override
    public void acquireLock(String tokenName, int maxWaitMs) throws Exception {
        System.out.println("acquireLock called");

        long start = System.currentTimeMillis();
        while (Files.exists(lockFile)) {
            if (System.currentTimeMillis() - start > maxWaitMs) {
                Files.deleteIfExists(lockFile); // remove stale lock
                break;
            }
            Thread.sleep(100);
        }
        Files.createFile(lockFile);
    }

    @Override
    public void releaseLock(String tokenName) throws Exception {
        System.out.println("releaseLock called");
        Files.deleteIfExists(lockFile);
    }
}

このクラスは、外部ストレージから既存の OAuth トークンを読み取り、認証成功後に新しいトークンを書き込みます。また、ロックファイルを使用してトークンリフレッシュ時の競合状態を防止し、コンテキスト値を通じてユーザー固有のトークン分離を実現します。

エラーハンドリングの考慮事項

上記の実装は基本的なトークン保存機能を提供しますが、本番環境へのデプロイでは以下のエラーシナリオを考慮してください:

  • ファイルパーミッション: アプリケーションがトークンファイルの保存場所に対して読み書き権限を持っていることを確認してください。適切なアクセス制御が設定された専用ディレクトリの使用を検討してください。
  • 破損したトークンファイル: トークンファイルが破損した場合、ドライバーは再認証を試みます。監視のためにこれらのイベントをログに記録することを検討してください。
  • 古いロックファイル: この実装には、古いロックを処理するためのタイムアウト機構が含まれています(93~96行目)。環境で想定されるトークンリフレッシュ時間に基づいて maxWaitMs を調整してください。
  • 同時アクセス: ロック機構により競合状態を防止できますが、信頼性を確保するために、ファイルシステムがアトミックなファイル操作をサポートしていることを確認してください。
  • トークンの有効期限: InitiateOAuth=GETANDREFRESH を設定すると CData ドライバーは期限切れのトークンを自動的にリフレッシュしますが、ログでリフレッシュ失敗を監視してください。

セキュリティのベストプラクティス: トークンファイルは、アクセス権限が制限された安全なディレクトリに保存してください。本番環境では、ファイルベースのストレージの代わりに、保存データの暗号化やデータベースバックエンドのトークンストレージの実装を検討してください。

ステップ 2: トークンストアファクトリの作成

このクラスは ITokenStoreFactory を実装し、トークンストアのインスタンスを生成します。

package main;

import cdata.jdbc.googlesheets.ITokenStore;
import cdata.jdbc.googlesheets.ITokenStoreFactory;

public class GoogleSheetsTokenStoreFactory implements ITokenStoreFactory {

    @Override
    public ITokenStore getStore(String context) throws Exception {
        return new GoogleSheetsTokenStore(context);
    }
}

このファクトリクラスは、OAuthSettingsLocation で定義されたコンテキストを受け取り、新しい GoogleSheetsTokenStore インスタンスを作成して返します。これにより、同じ実装を複数の接続で再利用できます。

ステップ 3: アプリケーションエントリポイントの構築

これはメインのアプリケーションエントリポイントで、カスタムトークンストアを登録し、CData JDBC ドライバーを使用して接続を確立します。

package main;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;

import cdata.jdbc.googlesheets.GoogleSheetsDriver;

public class TokenStore {

    public static void main(String[] args) throws Exception {

        // Register custom token store factory (must be done before getConnection)
        GoogleSheetsDriver.setTokenStoreFactory(
            new GoogleSheetsTokenStoreFactory()
        );

        String url =
            "jdbc:googlesheets:" +
            "InitiateOAuth=GETANDREFRESH;" +
            "OAuthSettingsLocation=externalToken://user=testuser;" +
            "OAuthClientId=<YOUR_CLIENT_ID>;" +
            "OAuthClientSecret=<YOUR_CLIENT_SECRET>;" +
            "CallbackURL=http://localhost:33333;";

        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT Name FROM Spreadsheets");

        while (rs.next()) {
            System.out.println("Spreadsheet: " + rs.getString("Name"));
        }

        conn.close();
    }
}

このメインクラスは、接続の確立前にカスタム ITokenStoreFactory を登録し、認証が必要な場合に OAuth フローを開始し、認証とトークンの再利用を検証するためのテストクエリを実行します。

アプリケーションの実行と検証

TokenStore.java ファイルを実行して、実装をテストしてみましょう。

  1. OAuth 認証のためにブラウザウィンドウが開きます
  2. 「Continue」または「Allow」をクリックしてアクセスを許可します
  3. 認証が完了すると、カスタムトークンストアを使用してトークンが保存されます
  4. アプリケーションは以下のような出力を表示します:

よくある問題のトラブルシューティング

OAuth 時にブラウザが開かない

OAuth 認証中にブラウザが開かない場合は、以下を確認してください:

  • CallbackURL が正しく設定されていることを確認します(例:http://localhost:33333
  • ポート 33333 が別のアプリケーションで使用されていないか確認します
  • ファイアウォールが CData ドライバーの localhost でのリッスンを許可していることを確認します
  • コンソール出力に表示される OAuth URL を手動でブラウザから開いてみてください

再実行時にトークンファイルが見つからない

接続のたびに認証が求められる場合は、以下を確認してください:

  • トークンファイルのパスにアクセス可能で、アプリケーションに書き込み権限があることを確認します
  • コンテキスト値(externalToken://user=testuser)が接続間で一貫していることを確認します
  • 実行間でトークンファイルが削除されていないことを確認します
  • 接続文字列で InitiateOAuth=GETANDREFRESH が設定されていることを確認します

「Invalid OAuth credentials」エラー

無効な資格情報で認証が失敗する場合は、以下を確認してください:

  • OAuth アプリの登録で取得した OAuthClientIdOAuthClientSecret の値を再確認します
  • 接続文字列の CallbackURL が、OAuth アプリに登録されている値と一致していることを確認します
  • OAuth アプリに Google Sheets 用の正しいスコープ/パーミッションが有効になっていることを確認します
  • トークンファイルを削除して、最初から再認証を試してみてください

GoogleSheetsDriver の ClassNotFoundException

ドライバークラスが見つからない場合は、以下を確認してください:

  • cdata.jdbc.googlesheets.jar がクラスパスに含まれていることを確認します
  • JAR ファイルが破損していないことを確認します(必要に応じて再ダウンロードしてください)
  • IDE がライブラリの依存関係を正しく読み込んでいることを確認します

今すぐ始めましょう

CData JDBC ドライバーは、組み込みの OAuth トークン管理とカスタマイズ可能なストレージオプションを備え、300 を超えるデータソースへの接続を提供します。CData での組み込み接続について詳しくはこちらをご覧いただくか、接続のエキスパートとの相談を予約して、トークンストレージの要件についてご相談ください。