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

總覽

用途:本文件說明如何使用 GoogleCredential 公用程式類別,透過 Google 服務進行 OAuth 2.0 授權。如要瞭解我們提供的一般 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 說明文件:

Google API 控制台

您必須先在 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))。

大多數應用程式都需要保存憑證的存取權杖和/或更新權杖。如要保留憑證的存取權和/或重新整理權杖,您可以使用 StoredCredential 自行實作 DataStoreFactory,或使用程式庫提供的下列其中一種實作方式:

AppEngine 使用者: AppEngineCredentialStore 已淘汰,不久後就會移除。建議您搭配使用 AppEngineDataStoreFactoryStoredCredential。 如果您已以舊有方式儲存憑證,可以使用新增的輔助方法 migrationTo(AppEngineDataStoreFactory)migrationTo(DataStore) 執行遷移作業。

您也可以使用 DataStoreCredentialRefreshListener,透過 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) 設為憑證。

授權碼流程

使用授權碼流程,讓使用者可授予應用程式存取 Google API 上受保護資料的權限。這個流程的通訊協定請參閱授權代碼授權頁面。

此流程是使用 GoogleAuthorizationCodeFlow 進行實作。步驟如下:

如果您並未使用 GoogleAuthorizationCodeFlow,則可使用較低層級的類別:

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

網路伺服器應用程式

請參閱「針對網路伺服器應用程式使用 OAuth 2.0」一文,瞭解此流程的通訊協定。

這個程式庫提供 JAR 輔助類別,可大幅簡化基本用途的授權碼流程。您只需提供 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 上的授權碼流程與 AVD 授權碼流程幾乎相同,唯一差別在於我們可以利用 Google App Engine 的 Users Java API。使用者必須先登入,才能啟用 Users Java API。如要瞭解如何將使用者重新導向至登入頁面,請參閱 web.xml 中的安全性與驗證

與 JAR 案例的主要差異在於,您須提供 AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (來自 google-oauth-client-appengine) 的具體子類別。這些類別會擴充抽象 AVD 類別,並使用 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 控制台下載的私密金鑰來簽署存取權杖的要求。

取自 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」中所述的瀏覽器用戶端流程,您通常可以遵循以下步驟:

  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 服務程式庫中,您可以使用 Java 適用的 Google API 用戶端程式庫,此程式庫支援 Android 4.0 (Ice Cream Sandwich) (或以上版本),詳情請見下文。適用於 Java 的 Google API 用戶端程式庫支援 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;
  }
}