OAuth 2.0 と Java 用 Google OAuth クライアント ライブラリ

概要

目的: このドキュメントでは、Google Cloud で提供される OAuth 2.0 の一般的な機能について説明します。 Java 用 Google OAuth クライアントライブラリを 使用しますこれらの関数を使用して 認証および認可。

GoogleCredential を使用して OAuth 2.0 認証を行う手順について、 詳しくは、 Java 用 Google API クライアント ライブラリで OAuth 2.0 を使用する

まとめ: OAuth 2.0 は、 エンド ユーザーが安全にクライアントを承認できるようにするための標準仕様。 保護されたサーバーサイド リソースにアクセスする。また、 OAuth 2.0 署名なしトークン アクセス メカニズムを使用して、これらの保護されたリソースにアクセスする方法を 付与される必要があります。

詳細については、次のパッケージの Javadoc のドキュメントをご覧ください。

クライアントの登録

Java 用 Google OAuth クライアント ライブラリを使用する前に、 認証サーバーにアプリケーションを登録してクライアント ID を受け取り、 クライアント シークレット。(このプロセスに関する一般的な情報については、 クライアント 登録の仕様をご覧ください)。

認証情報と認証情報ストア

認証情報 は、スレッドセーフの OAuth 2.0 ヘルパー クラスです。 できます。更新トークンを使用する場合、Credential はアクセスも更新します。 更新トークンを使用してアクセス トークンが期限切れになったときに、トークンを再発行します。たとえば アクセス トークンがある場合は、次の方法でリクエストできます。

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

ほとんどのアプリケーションでは、認証情報のアクセス トークンと 認証ページにリダイレクトされないように、 表示されます。「 CredentialStore このライブラリの実装は非推奨となったため、今後削除される予定です。 できます。別の方法として、 DataStoreFactory および DataStore インターフェースとインターフェース StoredCredential、 Kubernetes によって提供される Java 用の Google HTTP クライアント ライブラリ

ライブラリで提供されている次のいずれかの実装を使用できます。

Google App Engine ユーザー:

AppEngineCredentialStore は非推奨となったため、今後削除されます。

Google Cloud コンソールの AppEngineDataStoreFactory StoredCredential を使用します。 認証情報を以前の方法で保存している場合は、追加されたヘルパー メソッドを使用できます migrateTo(AppEngineDataStoreFactory) または migrateTo(DataStore) 移行する必要があります。

DataStoreCredentialRefreshListener を使用する 使用して認証情報に GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

認可コードフロー

認可コードフローを使用して、エンドユーザーがアプリケーションに認証を許可できるようにする アクセスできるようになります。このフローのプロトコルは、 認可コード付与の仕様

このフローは、 AuthorizationCodeFlow: ステップは次のとおりです。

または、Cloud Storage バケットを AuthorizationCodeFlow、 下位レベルのクラスを使用できます。

サーブレット認可コードフロー

このライブラリが提供するサーブレット ヘルパークラスを使用すると、 認証コードフローについて説明します。具体的なサブクラスを指定するだけで / AbstractAuthorizationCodeServlet および AbstractAuthorizationCodeCallbackServletgoogle-oauth-client-servlet から) web.xml ファイルに追加しますユーザー、グループ チャット、 ログインし、ユーザー ID を抽出します。

サンプルコード:

public class ServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class ServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

Google App Engine の認可コードフロー

App Engine の認可コードフローはサーブレットとほぼ同じ 使用する場合を除いて、Google App Engine の Users Java API。 Users Java API を有効にするには、ユーザーがログインしている必要があります。 ログインページへのユーザーのリダイレクトに関する情報(まだリダイレクトしていない場合) ログインしている場合は、 セキュリティと認証 (web.xml 内)。

サーブレットの場合との主な違いは、 サブクラス AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServletgoogle-oauth-client-appengine から)。抽象サーブレット クラスを拡張し、Users Java API を使用して getUserId メソッドを実装します。Google App Engine Data Store API を使用して認証情報を保存するには、AppEngineDataStoreFactoryJava 用 Google HTTP クライアント ライブラリのもの)を使用することをおすすめします。

サンプルコード:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

コマンドライン認証コードフロー

以下から抜粋した簡略化したサンプルコード dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

ブラウザベースのクライアント フロー

これらは、 暗黙的権限付与の仕様:

  • BrowserClientRequestUrl を使用すると、 エンドユーザーのブラウザから認証ページにリダイレクトされ、そこでエンドユーザーは 保護対象データへのアクセス権をアプリケーションに付与します。
  • JavaScript アプリケーションを使用して、URL 内で見つかったアクセス トークンを処理する 認証サーバーに登録されているリダイレクト URI のフラグメント。

ウェブ アプリケーションの使用例:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

期限切れのアクセス トークンの検出

OAuth 2.0 署名なし仕様によると、 アクセス期限が切れた保護されたリソースにアクセスするためにサーバーが呼び出されたとき サーバーは HTTP 401 Unauthorized ステータス コードを返します。 次に例を示します。

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

ただし、この仕様にはかなりの柔軟性があるようです。対象 OAuth 2.0 プロバイダのドキュメントをご覧ください。

もう 1 つの方法は、expires_in アクセス トークン レスポンス。 これは、許可されたアクセス トークンの存続時間を秒単位で指定します。 通常は 1 時間ですただし、最後にアクセス トークンが期限切れにならない場合もあります。 その間は引き続きアクセスが許可されます。だからこそ 通常は、メインスレッドではなく 401 Unauthorized ステータス コードを待つことをおすすめします。 経過時間に基づいてトークンの有効期限が切れていると仮定します。別の方法として、 期限切れの直前にアクセス トークンを更新しようとして、トークン サーバーが が使用できない場合は、401 を受け取るまでアクセス トークンを引き続き使用してください。この これは、Chronicle ではデフォルトで 認証情報

もう 1 つの方法は、すべてのリクエストの前に新しいアクセス トークンを取得することですが、 トークン サーバーへの追加の HTTP リクエストを毎回必要とするため、 速度とネットワーク使用量の点で 適した選択肢ではありません理想的には、アクセス トークンを 安全な永続ストレージに格納し、アプリケーションからの新しいアクセス リクエストを最小限に抑えます。 使用します。(ただし、インストール済みのアプリケーションの場合、安全なストレージは難しい問題です)。

アクセス トークンが無効になる理由としては、有効期限、 たとえば、ユーザーがトークンを明示的に取り消した場合などは、 エラー処理コードは堅牢ですトークンがもう存在しないことを検出したら、 期限切れになった場合や取り消された場合などは、そのアクセス権を トークンを取得できます。たとえば Android では、 AccountManager.invalidateAuthToken