OAuth 2.0 和 Java 適用的 Google OAuth 用戶端程式庫

總覽

目的:本文件說明 Google OAuth 用戶端程式庫您可以運用這些函式 驗證及授權。

如需使用 GoogleCredential 執行 OAuth 2.0 授權的操作說明 Google 服務,請參閱 將 OAuth 2.0 與適用於 Java 的 Google API 用戶端程式庫搭配使用

摘要: OAuth 2.0 是 可讓使用者安全地授權用戶端的標準規格 存取受保護的伺服器端資源。此外, OAuth 2.0 不記名權杖 規格說明如何使用存取權 取得的權杖

如需詳細資訊,請參閱下列套件的 Javadoc 文件:

客戶註冊

使用 Java 適用的 Google OAuth 用戶端程式庫之前,您可能需要 透過授權伺服器註冊您的應用程式,以便接收用戶端 ID 和 用戶端密鑰。(如需關於此程序的一般資訊,請參閱 客戶 註冊規格)。

憑證和憑證存放區

認證 是一種執行緒安全 OAuth 2.0 輔助類別,用於存取受保護的資源,並使用 存取權杖使用更新權杖時,Credential 也會重新整理存取權 權杖。舉例來說,如果您在 已有存取權杖,您可以透過下列方式提出要求:

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

大多數應用程式都需要保存憑證的存取權杖, 更新權杖,以免日後無法重新導向至授權 網頁。 CredentialStore 這個程式庫中的實作方式已淘汰,將於日後移除 版本。另一種方法是使用 DataStoreFactoryDataStore 介面集 StoredCredential、 編碼器-解碼器架構 適用於 Java 的 Google HTTP 用戶端程式庫

您可以使用程式庫提供的下列任一實作項目:

Google App Engine 使用者:

AppEngineCredentialStore 已淘汰,並即將移除。

建議您使用 AppEngineDataStoreFactory 使用 StoredCredential。 如果您以舊方式儲存憑證,可以使用新增的輔助方法。 migrateTo(AppEngineDataStoreFactory)migrateTo(DataStore) 以便進行遷移

使用 DataStoreCredentialRefreshListener 並將其設為 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

授權碼流程

使用授權碼流程,讓使用者授予您的應用程式 保護資料的存取權這個流程的通訊協定已在 授權碼授權規格

這個流程是以 AuthorizationCodeFlow。 步驟如下:

如果您不是使用 AuthorizationCodeFlow、 您可以使用較低層級的類別:

JS 授權碼流程

這個程式庫提供 CSP 輔助類別,以大幅簡化 基本用途的授權碼流程。您只需提供具體的子類別 / AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (來自 google-oauth-client-servlet) 然後新增到 web.xml 檔案中請注意,您仍然需要為使用者 登入網頁應用程式,並擷取使用者 ID。

程式碼範例:

public class ServletSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class ServletCallbackSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .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)。它們會擴充抽象 xls 類別,並使用 Users Java API 為您實作 getUserId 方法。AppEngineDataStoreFactory (來自 Java 適用的 Google HTTP 用戶端程式庫) 使用 Google App Engine Data Store API 保存憑證是個好方法。

程式碼範例:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

指令列授權碼流程

簡化的範例程式碼 dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

瀏覽器用戶端流程

以下是瀏覽器用戶端流程中,指定的 Implicit Grant 規格

  • 使用 BrowserClientRequestUrl 時, 將使用者的瀏覽器重新導向至授權頁面,讓使用者在這個頁面中 授予應用程式存取受保護資料的權限。
  • 使用 JavaScript 應用程式處理網址中的存取權杖 位於授權伺服器註冊的重新導向 URI 片段。

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

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

偵測過期的存取權杖

根據 OAuth 2.0 不記名規格, 伺服器呼叫伺服器存取受保護的資源,但存取權已過期時 伺服器通常會傳回 HTTP 401 Unauthorized 狀態碼 例如:

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

然而,規格中的彈性似乎很大。適用對象 詳細資料,請參閱 OAuth 2.0 供應商的說明文件。

另一種方法是檢查以下程式碼中的 expires_in 參數: 存取權杖回應。 這會指定授予存取權杖的生命週期 (以秒為單位), 通常一小時。但是,存取權杖實際上可能不會在最後過期 ,而伺服器可能會繼續允許存取。正因如此 一般而言,我們會建議您等待 401 Unauthorized 狀態碼,而非 並假設憑證已過期。此外,也可以 如果存取權杖到期,請盡快重新整理;如果存取權杖 無法使用,請繼續使用存取權杖,直到您收到 401 為止。這個 是 Google Analytics 中預設採用的策略 憑證

另一種方法是在每個請求之前擷取新的存取權杖,但 每次都必須向權杖伺服器發出額外的 HTTP 要求,因此很有可能 但速度和網路用量選擇不當在理想情況下 建立在安全的永久儲存空間,可盡量減少應用程式對新存取的要求數量 符記(但是,對於已安裝的應用程式而言,安全儲存空間是個很困難的問題)。

請注意,除了到期時間外,存取權杖可能會失效的原因, 例如使用者已明確撤銷權杖 錯誤處理程式碼是完善的。系統偵測到 有效,例如已過期或撤銷,您必須移除存取權 取得儲存空間憑證以 Android 裝置為例,您必須呼叫 AccountManager.invalidateAuthToken