はじめに (対象読者・この記事でわかること)

この記事は、JavaでWebアプリケーション開発を行っている方、Google Cloud Platformを利用している方、APIの認証・認可を実装したい方を対象としています。特に、Google Cloud Endpointsを使用して、特定のAPIエンドポイントを認証済みユーザーのみが実行できるようにする方法について解説します。この記事を読むことで、Google Cloud EndpointsとFirebase Authenticationを連携させたユーザー限定APIの実装方法、具体的なコード例、デプロイ手順を理解できます。また、実装中によく発生する問題とその解決策についても学べるため、実際の開発現場で即役立つ知識を得ることができます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Javaの基本的な知識 - Google Cloud Platformの基本的な知識 - REST APIの基本的な知識 - Firebase Authenticationの基本的な知識

Google Cloud Endpointsとユーザー認証の概要

Google Cloud Endpointsは、Google Cloud上でAPIを簡単に作成・管理・デプロイできるサービスです。特にJavaを使用したバックエンドサービスとの連携に優れており、APIの仕様定義、クライアントライブラリの生成、モニタリングなどを自動化できます。

多くのWebアプリケーションでは、一部の機能やAPIを特定のユーザーのみに限定して提供する必要があります。例えば、個人データを取得するエンドポイントや、有料機能を利用するエンドポイントなどです。Google Cloud Endpointsでは、このような認証・認可要件を簡単に実装できます。

認証には通常、Firebase Authenticationを利用します。これにより、Googleアカウント、メールアドレス、電話番号などによるユーザー認証を簡単に実装できます。認証済みユーザーのみがアクセスできるエンドポイントでは、リクエストに含まれるIDトークンを検証することで、ユーザーの正当性を確認します。

特定エンドポイントをユーザー限定に実装する具体的な手順

ステップ1:Google Cloudプロジェクトのセットアップ

まず、Google Cloud Consoleで新しいプロジェクトを作成します。プロジェクト作成後、以下のAPIを有効にします: 1. Cloud Endpoints API 2. Cloud Trace API 3. Service Management API 4. Firebase Admin API

次に、プロジェクトに課金が有効になっていることを確認します。Cloud Endpointsを使用するには、課金が必須です。

Bash
# gcloudコマンドラインツールを使用してプロジェクトを設定 gcloud config set project [YOUR_PROJECT_ID] gcloud services enable endpoints.googleapis.com gcloud services enable trace.googleapis.com gcloud services enable servicemanagement.googleapis.com gcloud services enable firebase.googleapis.com

ステップ2:Firebase Authenticationの有効化

Firebase Authenticationを有効にし、認証方法を設定します。ここではメール/パスワード認証を例に説明します。

  1. Firebase ConsoleでAuthenticationセクションに移動
  2. 「始める」ボタンをクリック
  3. 「メール/パスワード」を有効にする
Java
// Firebase Admin SDKの初期化 FirebaseApp.initializeApp(new FirebaseOptions.Builder() .setCredential(FirebaseCredentials.getApplicationDefault()) .setDatabaseUrl("https://[YOUR_PROJECT_ID].firebaseio.com") .build());

ステップ3:Cloud Endpointsの設定

APIの仕様を定義するOpenAPI仕様ファイル(swagger.yaml)を作成します。このファイルでは、エンドポイントのパス、HTTPメソッド、パラメータ、リクエスト/レスポンス形式などを定義します。

Yaml
swagger: '2.0' info: title: User Limited API description: API with user-limited endpoints version: 1.0.0 host: [YOUR_PROJECT_ID].cloudfunctions.net x-google-endpoints: - name: "[YOUR_PROJECT_ID].cloudfunctions.net" allowCors: true schemes: - https paths: /public: get: summary: Public endpoint operationId: publicEndpoint x-google-backend: address: https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/publicFunction responses: '200': description: A successful response schema: type: string /private: get: summary: Private endpoint (user-limited) operationId: privateEndpoint x-google-backend: address: https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/privateFunction security: - firebase: [] responses: '200': description: A successful response schema: type: string securityDefinitions: firebase: authorizationUrl: "" flow: "bearer" name: "Firebase" type: "apiKey" x-google-issuer: "https://securetoken.google.com/[YOUR_PROJECT_ID]" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com" x-google-jwks: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys"

ステップ4:Javaバックエンドでの認証チェックの実装

Cloud Functions for Firebaseを使用して、Javaでバックエンドを実装します。認証が必要なエンドポイントでは、IDトークンを検証する処理を追加します。

Java
// 公開エンドポイント(認証不要) public String publicEndpoint() { return "This is a public endpoint that anyone can access."; } // プライベートエンドポイント(認証必須) public String privateEndpoint(HttpServletRequest request) throws Exception { // リクエストからAuthorizationヘッダーを取得 String idToken = request.getHeader("Authorization").replace("Bearer ", ""); // IDトークンを検証 FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken); String uid = decodedToken.getUid(); // 検証成功時の処理 return "Hello, " + uid + "! This is a private endpoint only authenticated users can access."; }

ステップ5:特定エンドポイントのユーザー限定設定

OpenAPI仕様ファイルで、特定のエンドポイントに認証要件を設定します。securityプロパティを使用して、エンドポイントごとに認証の有無を指定します。

Yaml
# 公開エンドポイント(認証不要) /public: get: # ... その他の設定 ... # プライベートエンドポイント(認証必須) /private: get: security: - firebase: [] # このエンドポイントではFirebase認証を必須にする # ... その他の設定 ...

ハマった点やエラー解決

問題1:IDトークンの検証で「Invalid ID token」エラーが発生

原因:リクエストヘッダーに含まれるIDトークンが無効、期限切れ、または不正な形式である。

解決策: 1. クライアント側でIDトークンが正しく取得されているか確認 2. トークンの有効期限を確認(デフォルトでは1時間) 3. トークンが改ざんされていないか検証

Java
// トークン検証時のエラーハンドリング try { FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken); String uid = decodedToken.getUid(); // ユーザーIDを使用した処理 } catch (FirebaseAuthException e) { // トークンが無効な場合 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return "Invalid ID token"; }

問題2:CORSエラーが発生

原因:Cloud Functionsのデプロイ時にCORS設定が正しく行われていない。

解決策: 1. Cloud Functionsのデプロイ時にCORSヘッダーを設定 2. OpenAPI仕様ファイルでallowCors: trueを設定

Java
// CORS対応のフィルター public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Access-Control-Allow-Origin", "*"); httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) { httpResponse.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(request, response); } } // ... 他のメソッドは省略 ... }

問題3:認証済みユーザーでも「403 Forbidden」エラー

原因:Cloud Endpointsのバックエンド設定で認証情報が正しく渡されていない。

解決策: 1. OpenAPI仕様ファイルのx-google-backend設定を確認 2. バックエンド関数でリクエストヘッダーが正しく渡されているか確認

Yaml
# 正しいx-google-backendの設定 x-google-backend: address: https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/privateFunction jwt_audience: [YOUR_PROJECT_ID] jwt_headers: id_token: Authorization

まとめ

本記事では、Google Cloud Endpointsを使用して特定のAPIエンドポイントを認証済みユーザーのみが実行できるようにする方法について解説しました。

  • Google CloudプロジェクトとFirebase Authenticationのセットアップ
  • OpenAPI仕様ファイルでの認証要件の定義
  • JavaバックエンドでのIDトークン検証の実装
  • CORSエラーなどの問題解決策

この記事を通して、Google Cloud Platform上で安全なAPIを構築するための具体的な知識を得られたことと思います。今後は、より高度な認可ロール(管理者、一般ユーザーなど)の実装や、リクエストレート制限などのセキュリティ対策についても記事にする予定です。

参考資料