搭配適用於 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 Console

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

大多數應用程式都需要保存憑證的存取權杖,並/或 重新整理權杖。如要保留憑證的存取權和/或更新權杖,您可以 讓使用者自行實作 DataStoreFactory 使用 StoredCredential; 您也可以使用程式庫提供的這些實作項目:

App Engine 使用者: AppEngineCredentialStore 並將於近期移除。建議您使用 AppEngineDataStoreFactory 使用 StoredCredential。 如果您有以舊方式儲存的憑證,可以使用新增的憑證 輔助方法 migrateTo(AppEngineDataStoreFactory)migrateTo(DataStore) 才能進行遷移作業

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

授權碼流程

使用授權碼流程,讓使用者授予您的應用程式 透過 Google API 存取受保護資料。本流程的通訊協定是 指定於 授權碼授權

這個流程是透過 GoogleAuthorizationCodeFlow 進行實作。 步驟如下:

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

Google API 控制台設定專案時, 系統會根據您使用的流程,選擇不同的憑證 詳情請參閱設定 OAuth 2.0OAuth 2.0 情境。 各流程的程式碼片段如下。

網路伺服器應用程式

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

這個程式庫提供 CSP 輔助類別,以大幅簡化 基本用途的授權碼流程。您只需提供具體的子類別 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 上的授權碼流程與 JDK 幾乎相同 但可以使用 Google App Engine 的 Users Java API。該使用者 必須登入,才能啟用 Users Java API;深入瞭解 如果使用者尚未登入,則將他們重新導向至登入頁面,請參閱 安全性和驗證 (在 web.xml 中)。

與 JDK 案例的主要差異在於 子類別 AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (來自 google-oauth-client-appengine。 他們會擴充抽象 JDK 類別,並實作 getUserId 方法 使用 Users Java APIAppEngineDataStoreFactory (來自 google-http-client-appengine) 使用 Google App Engine 資料來保留憑證 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 用戶端程式庫 處理在重新導向 URI 網址片段中找到的存取權杖 已在 Google API 控制台註冊。

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

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 服務程式庫 以獲得最佳效能和體驗。如果您是 Google API 您想使用 Android 系統並未納入 Google Play 服務程式庫, 可使用支援 Android 4.0 (Ice Cream Sandwich) 的 Google API 用戶端程式庫 (或更高版本),請參閱本文說明。透過 Google Cloud 控制台 Java 適用的 API 用戶端程式庫是 @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 控制台。 否則,AccountManager 只會為您提供 而且通常非常低相對地 金鑰獲得較高的免費配額,並可視需要設定帳單的使用情形 上方。

擷取的程式碼片段範例 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;
  }
}