概要
目的: このドキュメントでは、GoogleCredential ユーティリティ クラスを使用して、Google サービスで OAuth 2.0 による承認を行う方法について説明します。Google が提供する一般的な OAuth 2.0 関数については、OAuth 2.0 と Java 用 Google OAuth クライアント ライブラリをご覧ください。
概要: Google サービスに保存されている保護されたデータにアクセスするには、OAuth 2.0 を使用して承認を行います。Google API は、さまざまな種類のクライアント アプリケーションで OAuth 2.0 フローをサポートしています。いずれのフローでも、クライアント アプリケーションは、アクセスされるクライアント アプリケーションと保護されるデータのオーナーだけに関連付けられたアクセス トークンをリクエストします。アクセス トークンには、クライアント アプリケーションがアクセスできるデータの種類を定義する限定的なスコープも関連付けられています(例: 「タスクの管理」など)。OAuth 2.0 の重要な目標は、アクセス トークンが盗まれた場合の影響を最小限にしつつ、保護されたデータへの安全で便利なアクセスを提供することです。
Java 用 Google API クライアント ライブラリの OAuth 2.0 パッケージは、汎用の Java 用 Google OAuth 2.0 クライアント ライブラリを基に構築されています。
詳細については、次のパッケージの Javadoc ドキュメントをご覧ください。
- com.google.api.client.googleapis.auth.oauth2(google-api-client から)
- com.google.api.client.googleapis.extensions.appengine.auth.oauth2(google-api-client-appengine から)
Google API Console
Google API にアクセスする前に、Google API Console で、クライアントがインストール アプリケーション、モバイルアプリ、ウェブサーバー、またはブラウザで実行されているクライアントなど、認証と請求の目的でプロジェクトを設定する必要があります。
認証情報を正しく設定する方法については、API Console ヘルプをご覧ください。
認証情報
GoogleCredential
GoogleCredential は、アクセス トークンを使用して保護されたリソースにアクセスするための OAuth 2.0 のスレッドセーフなヘルパークラスです。たとえば、アクセス トークンがすでにある場合は、次の方法でリクエストを行うことができます。
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken); Plus plus = new Plus.builder(new NetHttpTransport(), GsonFactory.getDefaultInstance(), credential) .setApplicationName("Google-PlusSample/1.0") .build();
Google App Engine ID
この代替認証情報は、Google App Engine App Identity Java API に基づいています。クライアント アプリケーションがエンドユーザーのデータへのアクセスを要求する認証情報とは異なり、App Identity API はクライアント アプリケーション自身のデータへのアクセスを提供します。
AppIdentityCredential(google-api-client-appengine から使用)を使用します。この認証情報は、Google App Engine がすべての詳細を処理するので、はるかに簡単です。必要な OAuth 2.0 スコープのみを指定します。
urlshortener-robots-appengine-sample から取得したサンプルコードを以下に示します。
static Urlshortener newUrlshortener() { AppIdentityCredential credential = new AppIdentityCredential( Collections.singletonList(UrlshortenerScopes.URLSHORTENER)); return new Urlshortener.Builder(new UrlFetchTransport(), GsonFactory.getDefaultInstance(), credential) .build(); }
データストア
通常、アクセス トークンの有効期限は 1 時間です。有効期限を設定しようとすると、エラーが発生します。GoogleCredential によってトークンの「更新」が自動的に処理されます。つまり、単に新しいアクセス トークンを取得するだけです。これは、有効期限の長い更新トークンを使用することで行われます。通常、認証コードフロー中に access_type=offline
パラメータを使用すると、アクセス トークンとともに受信されます(GoogleAuthorizationCodeFlow.Builder.setAccessType(String) をご覧ください)。
ほとんどのアプリケーションでは、認証情報のアクセス トークンや更新トークンを保持する必要があります。認証情報のアクセス トークンや更新トークンを保持するには、DataStoreFactory の独自の実装を StoredCredential で指定するか、ライブラリが提供する次のいずれかの実装を使用できます。
- AppEngineDataStoreFactory: Google App Engine Data Store API を使用して認証情報を保存します。
- MemoryDataStoreFactory: メモリに認証情報が保持されています。これは、プロセスの存続期間中、短期的なストレージとしてのみ役立ちます。
- FileDataStoreFactory: ファイル内に認証情報を保持します。
AppEngine ユーザー: AppEngineCredentialStore は非推奨であり、まもなく削除されます。AppEngineDataStoreFactory と StoredCredential を組み合わせて使用することをおすすめします。以前の方法で認証情報が保存されている場合は、追加されたヘルパー メソッド migrateTo(AppEngineDataStoreFactory) または migrateTo(DataStore) を使用して移行を行うことができます。
DataStoreCredentialRefreshListener を使用し、GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) を使用して認証情報に設定できます。
認証コードフロー
認証コードフローを使用して、エンドユーザーがアプリケーションによる Google API で保護されたデータへのアクセスを許可できるようにします。このフローのプロトコルは認証コード付与で指定されています。
このフローは、GoogleAuthorizationCodeFlow を使用して実装されています。ステップは次のとおりです。
- エンドユーザーがアプリケーションにログインします。そのユーザーを、アプリケーションに固有のユーザー ID に関連付ける必要があります。
- ユーザー ID に基づいて AuthorizationCodeFlow.loadCredential(String)) を呼び出し、エンドユーザーの認証情報がすでにわかっているかどうかを確認します。その場合は Google が対応いたします。
- そうでない場合は、AuthorizationCodeFlow.newAuthorizationUrl() を呼び出して、エンドユーザーのブラウザを認可ページに誘導し、アプリケーションによる保護されたデータへのアクセスを許可します。
- Google 承認サーバーは、
code
クエリ パラメータとともに、アプリケーションで指定されたリダイレクト URL にブラウザをリダイレクトします。code
パラメータを使用し、AuthorizationCodeFlow.newTokenRequest(String) を使用してアクセス トークンをリクエストします。 - AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) を使用して、保護されたリソースにアクセスするための認証情報を保存し、取得します。
GoogleAuthorizationCodeFlow を使用していない場合は、下位レベルのクラスを使用できます。
- DataStore.get(String) を使用して、ユーザー ID に基づいてストアから認証情報を読み込みます。
- GoogleAuthorizationCodeRequestUrl を使用して、ブラウザを認可ページに誘導します。
- AuthorizationCodeResponseUrl を使用して認証レスポンスを処理し、認証コードを解析します。
- GoogleAuthorizationCodeTokenRequest を使用してアクセス トークン、場合によっては更新トークンをリクエストします。
- 新しい GoogleCredential を作成し、DataStore.set(String, V) を使用して保存する。
GoogleCredential
を使用して、保護されたリソースにアクセスします。期限切れのアクセス トークンは、更新トークン(該当する場合)を使用して自動的に更新されます。DataStoreCredentialRefreshListener を使用し、GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) を使用して認証情報に設定します。
Google API Console でプロジェクトを設定する際には、使用しているフローに応じてさまざまな認証情報を選択します。詳しくは、OAuth 2.0 の設定と OAuth 2.0 のシナリオをご覧ください。各フローのコード スニペットを以下に示します。
ウェブサーバーアプリケーション
このフローのプロトコルについては、ウェブサーバー アプリケーションに OAuth 2.0 を使用するをご覧ください。
このライブラリは、基本的なユースケースの認証コードフローを大幅に簡素化するためのサーブレット ヘルパークラスを提供します。AbstractAuthorizationCodeServlet と AbstractAuthorizationCodeCallbackServlet の具体的なサブクラス(google-oauth-client-servlet 上)を指定し、web.xml ファイルに追加するだけです。ウェブ アプリケーションのユーザー ログインに対応し、ユーザー ID を抽出する必要があります。
public class CalendarServletSample 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 GoogleAuthorizationCodeFlow.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance(), "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]", Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").build(); } @Override protected String getUserId(HttpServletRequest req) throws ServletException, IOException { // return user ID } } public class CalendarServletCallbackSample 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 GoogleAuthorizationCodeFlow.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance() "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]", Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").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 の [セキュリティと認証] をご覧ください。
サーブレットの場合との主な違いは、AbstractAppEngineAuthorizationCodeServlet と AbstractAppEngineAuthorizationCodeCallbackServlet の具体的なサブクラス(google-oauth-client-appengine から)を提供することです。抽象サーブレット クラスを拡張し、Users Java API を使用して getUserId
メソッドを実装します。AppEngineDataStoreFactory(google-http-client-appengine から)は、Google App Engine Data Store API を使用して認証情報を保持するための優れたオプションです。
calendar-appengine-sample から取得した例(若干変更)。
public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // do stuff } @Override protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { return Utils.getRedirectUri(req); } @Override protected AuthorizationCodeFlow initializeFlow() throws IOException { return Utils.newFlow(); } } class Utils { static String getRedirectUri(HttpServletRequest req) { GenericUrl url = new GenericUrl(req.getRequestURL().toString()); url.setRawPath("/oauth2callback"); return url.build(); } static GoogleAuthorizationCodeFlow newFlow() throws IOException { return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").build(); } } public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet { private static final long serialVersionUID = 1L; @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 { String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname(); resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>"); resp.setStatus(200); resp.addHeader("Content-Type", "text/html"); } @Override protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { return Utils.getRedirectUri(req); } @Override protected AuthorizationCodeFlow initializeFlow() throws IOException { return Utils.newFlow(); } }
その他の例については、storage-serviceaccount-appengine-sample をご覧ください。
サービス アカウント
GoogleCredential はサービス アカウントもサポートしています。クライアント アプリケーションがエンドユーザーのデータへのアクセスをリクエストする認証情報とは異なり、サービス アカウントはクライアント アプリケーション独自のデータへのアクセスを提供します。クライアント アプリケーションが、Google API Console からダウンロードした秘密鍵を使用して、アクセス トークンのリクエストに署名します。
plus-serviceaccount-cmdline-sample から取得したコード例:
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); ... // Build service account credential. GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json")) .createScoped(Collections.singleton(PlusScopes.PLUS_ME)); // Set up global Plus instance. plus = new Plus.Builder(httpTransport, jsonFactory, credential) .setApplicationName(APPLICATION_NAME).build(); ...
その他の例については、storage-serviceaccount-cmdline-sample をご覧ください。
なりすまし
サービス アカウント フローを使用して、所有しているドメインのユーザーになりすましすることもできます。これは前述のサービス アカウントのフローと非常によく似ていますが、さらに GoogleCredential.Builder.setServiceAccountUser(String) を呼び出します。
インストール型アプリケーション
これは、インストール済みアプリケーションへの OAuth 2.0 の使用で説明されているコマンドライン認証コードフローです。
plus-cmdline-sample から抜粋したサンプル スニペット:
public static void main(String[] args) { try { httpTransport = GoogleNetHttpTransport.newTrustedTransport(); dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR); // authorization Credential credential = authorize(); // set up global Plus instance plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName( APPLICATION_NAME).build(); // ... } private static Credential authorize() throws Exception { // load client secrets GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json"))); // set up authorization code flow GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( httpTransport, JSON_FACTORY, clientSecrets, Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory( dataStoreFactory).build(); // authorize return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user"); }
クライアント側アプリケーション
クライアント側アプリケーションに OAuth 2.0 を使用するで説明されているブラウザベースのクライアント フローを使用するには、通常次の手順を行います。
- GoogleBrowserClientRequestUrl を使用して、ブラウザでエンドユーザーを認証ページにリダイレクトし、エンドユーザーの保護されたデータへのアクセスをブラウザ アプリケーションに許可します。
- JavaScript 用 Google API クライアント ライブラリを使用して、Google API Console に登録されているリダイレクト URI で URL フラグメント内のアクセス トークンを処理します。
ウェブ アプリケーションの使用例:
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException { String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com", "https://oauth2.example.com/oauthcallback", Arrays.asList( "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build(); response.sendRedirect(url); }
Android
Android で使用するライブラリ:
Android 向けの開発中で、使用する Google API が Google Play 開発者サービス ライブラリに含まれている場合は、最高のパフォーマンスとエクスペリエンスを実現するために、そのライブラリを使用してください。Android で使用する Google API が Google Play 開発者サービス ライブラリに含まれていない場合は、Android 4.0(Ice Cream Sandwich)以降に対応する Java の Google API クライアント ライブラリを使用できます。Java 用 Google API クライアント ライブラリでの Android のサポートは、@Beta です。
バックグラウンド:
Eclair(SDK 2.1)以降では、ユーザー アカウントは Android デバイスでアカウント マネージャーを使用して管理されます。Android アプリの承認はすべて、AccountManager を使用して SDK によって一元管理されます。アプリケーションに必要な OAuth 2.0 スコープを指定すると、使用するアクセス トークンが返されます。
OAuth 2.0 スコープは、authTokenType
パラメータを介して oauth2:
とスコープで指定します。例:
oauth2:https://www.googleapis.com/auth/tasks
Google Tasks API に対する読み取り/書き込みアクセス権を指定します。複数の OAuth 2.0 スコープが必要な場合は、スペース区切りリストを使用します。
一部の API には、特別な authTokenType
パラメータもあります。たとえば、「タスクを管理する」は、上記の authtokenType
の例のエイリアスです。
また、Google API Console から API キーを指定する必要もあります。それ以外の場合、AccountManager から提供されるトークンは、匿名割り当てのみを提供します。これは通常、非常に低くなっています。一方、API キーを指定すると、無料の割り当てが増え、必要に応じてその使用量を超える課金を設定できます。
tasks-android-sample から取得したコード スニペットの例:
com.google.api.services.tasks.Tasks service; @Override public void onCreate(Bundle savedInstanceState) { credential = GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS)); SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null)); service = new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential) .setApplicationName("Google-TasksAndroidSample/1.0").build(); } private void chooseAccount() { startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_GOOGLE_PLAY_SERVICES: if (resultCode == Activity.RESULT_OK) { haveGooglePlayServices(); } else { checkGooglePlayServicesAvailable(); } break; case REQUEST_AUTHORIZATION: if (resultCode == Activity.RESULT_OK) { AsyncLoadTasks.run(this); } else { chooseAccount(); } break; case REQUEST_ACCOUNT_PICKER: if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) { String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME); if (accountName != null) { credential.setSelectedAccountName(accountName); SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); editor.putString(PREF_ACCOUNT_NAME, accountName); editor.commit(); AsyncLoadTasks.run(this); } } break; } }