總覽
用途:本文件說明如何使用 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 說明文件:
- 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 控制台
您必須先在 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,或使用程式庫提供的下列其中一種實作方式:
- AppEngineDataStoreFactory:使用 Google App Engine Data Store API 保留憑證。
- MemoryDataStoreFactory:將憑證「保留」在記憶體中,該憑證僅可做為程序生命週期的短期儲存空間。
- FileDataStoreFactory:保留檔案中的憑證。
AppEngine 使用者: AppEngineCredentialStore 已淘汰,不久後就會移除。建議您搭配使用 AppEngineDataStoreFactory 與 StoredCredential。 如果您已以舊有方式儲存憑證,可以使用新增的輔助方法 migrationTo(AppEngineDataStoreFactory) 或 migrationTo(DataStore) 執行遷移作業。
您也可以使用 DataStoreCredentialRefreshListener,透過 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) 設為憑證。
授權碼流程
使用授權碼流程,讓使用者可授予應用程式存取 Google API 上受保護資料的權限。這個流程的通訊協定請參閱授權代碼授權頁面。
此流程是使用 GoogleAuthorizationCodeFlow 進行實作。步驟如下:
- 使用者登入您的應用程式。您需要將使用者與應用程式專屬的使用者 ID 建立關聯。
- 根據使用者 ID 呼叫 AuthorizationCodeFlow.loadCredential(String),檢查使用者憑證是否已經已知。如果是,這樣就大功告成了。
- 如果沒有,請呼叫 AuthorizationCodeFlow.newAuthorizationUrl(),並將使用者的瀏覽器導向授權頁面,授權應用程式存取其受保護資料。
- 接著,Google 授權伺服器會把瀏覽器重新導向至應用程式指定的重新導向網址,以及
code
查詢參數。使用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 控制台中設定專案時,您可以根據使用的流程選取不同的憑證。詳情請參閱設定 OAuth 2.0 和 OAuth 2.0 情境。以下為各個流程的程式碼片段。
網路伺服器應用程式
請參閱「針對網路伺服器應用程式使用 OAuth 2.0」一文,瞭解此流程的通訊協定。
這個程式庫提供 JAR 輔助類別,可大幅簡化基本用途的授權碼流程。您只需提供 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 上的授權碼流程與 AVD 授權碼流程幾乎相同,唯一差別在於我們可以利用 Google App Engine 的 Users Java API。使用者必須先登入,才能啟用 Users Java API。如要瞭解如何將使用者重新導向至登入頁面,請參閱 web.xml 中的安全性與驗證。
與 JAR 案例的主要差異在於,您須提供 AbstractAppEngineAuthorizationCodeServlet 和 AbstractAppEngineAuthorizationCodeCallbackServlet (來自 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」中所述的瀏覽器用戶端流程,您通常可以遵循以下步驟:
- 使用 GoogleBrowserClientRequestUrl 將瀏覽器使用者重新導向至授權頁面,授權瀏覽器應用程式存取使用者的受保護資料。
- 使用 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
適用於 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; } }