搭配適用於 Java 的 Google API 用戶端程式庫使用 OAuth 2.0

總覽

用途:本文說明如何使用 GoogleCredential 公用程式類別,透過 OAuth 2.0 授權存取 Google 服務。如要瞭解我們提供的通用 OAuth 2.0 函式,請參閱「OAuth 2.0 and the Google OAuth Client Library for Java」。

摘要:如要存取 Google 服務中儲存的受保護資料,請使用 OAuth 2.0 授權。Google API 支援不同類型用戶端應用程式的 OAuth 2.0 流程。 在所有這些流程中,用戶端應用程式會要求與您的用戶端應用程式和受保護資料擁有者相關聯的存取權杖。存取權杖也會與有限的範圍建立關聯,定義用戶端應用程式可存取的資料類型 (例如「管理您的工作」)。OAuth 2.0 的重要目標是提供安全且便利的受保護資料存取權,同時盡量減少存取權杖遭竊時的潛在影響。

Java 適用的 Google API 用戶端程式庫中的 OAuth 2.0 套件,是以一般用途的 Java 適用的 Google OAuth 2.0 用戶端程式庫為基礎建構而成。

詳情請參閱下列套件的 Javadoc 說明文件:

Google API Console

無論用戶端是已安裝的應用程式、行動應用程式、網頁伺服器,還是瀏覽器中執行的用戶端,您都必須先在 Google API 控制台中設定專案,才能存取 Google API,以進行驗證和帳單作業。

如需正確設定憑證的操作說明,請參閱 API 控制台說明

憑證

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 身分

這個替代憑證是以 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))。

大多數應用程式都需要保存憑證的存取權杖和/或重新整理權杖。如要保存憑證的存取和/或更新權杖,您可以提供 DataStoreFactoryStoredCredential 實作項目;也可以使用程式庫提供的下列任一實作項目:

AppEngine 使用者: AppEngineCredentialStore 已淘汰,即將移除。建議您搭配 StoredCredential 使用 AppEngineDataStoreFactory。 如果憑證是以舊方式儲存,可以使用新增的輔助方法 migrateTo(AppEngineDataStoreFactory)migrateTo(DataStore) 進行遷移。

您可以使用 DataStoreCredentialRefreshListener,並使用 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) 為憑證設定該監聽器。

授權碼流程

使用授權碼流程,允許使用者授權應用程式存取 Google API 中受保護的資料。這個流程的通訊協定在「授權碼授權」中指定。

這個流程是使用 GoogleAuthorizationCodeFlow 實作。步驟如下:

或者,如果您未使用 GoogleAuthorizationCodeFlow,可以改用較低層級的類別:

Google API 控制台中設定專案時,請根據使用的流程選取不同憑證。詳情請參閱「設定 OAuth 2.0」和「OAuth 2.0 使用情境」。以下是各個流程的程式碼片段。

網路伺服器應用程式

如要瞭解這個流程的通訊協定,請參閱「使用 OAuth 2.0 處理網路伺服器應用程式」。

這個程式庫提供 Servlet 輔助類別,可大幅簡化基本用途的授權碼流程。您只需提供 AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (來自 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 的授權碼流程與 Servlet 授權碼流程幾乎相同,但我們可以運用 Google App Engine 的 Users Java API。使用者必須登入,才能啟用 Users Java API;如要瞭解如何將未登入的使用者重新導向至登入頁面,請參閱「安全性與驗證」(位於 web.xml 中)。

與 Servlet 案例的主要差異在於,您提供 AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet 的具體子類別 (來自 google-oauth-client-appengine)。這些類別會擴充抽象 servlet 類別,並使用 Users Java API 為您實作 getUserId 方法。AppEngineDataStoreFactory (來自 google-http-client-appengine) 是使用 Google App Engine DataStore 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 控制台下載的私密金鑰,簽署存取權杖要求。

使用範例:

HttpTransport httpTransport = new NetHttpTransport();
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 授權已安裝應用程式」一文所述的指令列授權碼流程。

使用範例:

public static void main(String[] args) {
  try {
    httpTransport = new NetHttpTransport();
    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」一文所述的瀏覽器型用戶端流程,通常會按照下列步驟操作:

  1. 使用 GoogleBrowserClientRequestUrl 將瀏覽器中的使用者重新導向至授權頁面,授予瀏覽器應用程式存取使用者受保護資料的權限。
  2. 使用 適用於 JavaScript 的 Google API 用戶端程式庫,處理在 Google API 控制台註冊的重新導向 URI 網址片段中找到的存取權杖。

網頁應用程式的用法範例:

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

@Beta

要搭配 Android 使用哪個程式庫:

如果您是為 Android 開發應用程式,且想使用的 Google API 包含在 Google Play 服務程式庫中,請使用該程式庫,以獲得最佳效能和體驗。如果您想搭配 Android 使用的 Google API 不屬於 Google Play 服務程式庫,可以使用支援 Android 4.0 (Ice Cream Sandwich) 以上版本的 Java 適用的 Google API 用戶端程式庫,詳情請參閱本文。Google API Java 專用用戶端程式庫的 Android 支援為 @Beta 版。

背景說明:

從 Eclair (SDK 2.1) 開始,Android 裝置上的使用者帳戶會透過帳戶管理工具進行管理。所有 Android 應用程式授權都由 SDK 使用 AccountManager 集中管理。您指定應用程式需要的 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 控制台中的 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;
  }
}