Sử dụng OAuth 2.0 với Thư viện ứng dụng API của Google cho Java

Tổng quan

Mục đích: Tài liệu này giải thích cách sử dụng lớp tiện ích GoogleCredential để thực hiện việc uỷ quyền OAuth 2.0 với các dịch vụ của Google. Để biết thông tin về các hàm OAuth 2.0 chung mà chúng tôi cung cấp, hãy xem OAuth 2.0 và Thư viện ứng dụng Google OAuth cho Java.

Tóm tắt: Để truy cập vào dữ liệu được bảo vệ và lưu trữ trên các dịch vụ của Google, hãy sử dụng OAuth 2.0 để uỷ quyền. Các API của Google hỗ trợ quy trình OAuth 2.0 cho nhiều loại ứng dụng khách. Trong tất cả các luồng này, ứng dụng khách sẽ yêu cầu một mã truy cập chỉ được liên kết với ứng dụng khách của bạn và chủ sở hữu của dữ liệu được bảo vệ mà ứng dụng đang truy cập. Mã truy cập cũng được liên kết với một phạm vi hạn chế xác định loại dữ liệu mà ứng dụng khách của bạn có quyền truy cập (ví dụ: "Quản lý việc cần làm"). Một mục tiêu quan trọng của OAuth 2.0 là cung cấp quyền truy cập an toàn và thuận tiện vào dữ liệu được bảo vệ, đồng thời giảm thiểu tác động tiềm ẩn nếu mã truy cập bị đánh cắp.

Các gói OAuth 2.0 trong Thư viện ứng dụng Google API cho Java được xây dựng trên Thư viện ứng dụng Google OAuth 2.0 cho Java đa năng.

Để biết thông tin chi tiết, hãy xem tài liệu Javadoc cho các gói sau:

Bảng điều khiển API của Google

Để có thể truy cập vào các API của Google, bạn cần thiết lập một dự án trên Google API Console cho mục đích xác thực và thanh toán, cho dù ứng dụng của bạn là ứng dụng đã cài đặt, ứng dụng di động, máy chủ web hay ứng dụng chạy trong trình duyệt.

Để biết hướng dẫn về cách thiết lập thông tin xác thực đúng cách, hãy xem Trợ giúp về API Console.

Thông tin xác thực

GoogleCredential

GoogleCredential là một lớp trợ giúp an toàn cho luồng OAuth 2.0 để truy cập vào các tài nguyên được bảo vệ bằng mã truy cập. Ví dụ: nếu đã có mã truy cập, bạn có thể đưa ra yêu cầu theo cách sau:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

Danh tính Google App Engine

Thông tin xác thực thay thế này dựa trên API Nhận dạng ứng dụng Google App Engine Java. Không giống như thông tin đăng nhập mà một ứng dụng khách yêu cầu quyền truy cập vào dữ liệu của người dùng cuối, App Identity API cung cấp quyền truy cập vào dữ liệu của chính ứng dụng khách.

Sử dụng AppIdentityCredential (từ google-api-client-appengine). Thông tin đăng nhập này đơn giản hơn nhiều vì Google App Engine sẽ xử lý tất cả thông tin chi tiết. Bạn chỉ cần chỉ định phạm vi OAuth 2.0 mà bạn cần.

Mã ví dụ lấy từ 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();
}

Kho dữ liệu

Mã truy cập thường có thời hạn là 1 giờ. Sau thời gian này, bạn sẽ gặp lỗi nếu cố gắng sử dụng mã đó. GoogleCredential sẽ tự động "làm mới" mã thông báo, tức là lấy một mã thông báo truy cập mới. Việc này được thực hiện bằng mã làm mới dài hạn. Mã này thường được nhận cùng với mã truy cập nếu bạn sử dụng tham số access_type=offline trong quy trình cấp mã uỷ quyền (xem GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

Hầu hết các ứng dụng sẽ cần duy trì mã truy cập và/hoặc mã làm mới của thông tin đăng nhập. Để duy trì quyền truy cập và/hoặc mã làm mới của thông tin đăng nhập, bạn có thể cung cấp chế độ triển khai riêng của DataStoreFactory bằng StoredCredential; hoặc bạn có thể sử dụng một trong các chế độ triển khai sau do thư viện cung cấp:

  • AppEngineDataStoreFactory: duy trì thông tin đăng nhập bằng API Data Store của Google App Engine.
  • MemoryDataStoreFactory: "duy trì" thông tin đăng nhập trong bộ nhớ, chỉ hữu ích khi là bộ nhớ ngắn hạn trong suốt vòng đời của quy trình.
  • FileDataStoreFactory: duy trì thông tin đăng nhập trong một tệp.

Người dùng AppEngine: AppEngineCredentialStore không còn được dùng nữa và sẽ sớm bị xoá. Bạn nên sử dụng AppEngineDataStoreFactory với StoredCredential. Nếu có thông tin đăng nhập được lưu trữ theo cách cũ, bạn có thể sử dụng các phương thức trợ giúp đã thêm migrateTo(AppEngineDataStoreFactory) hoặc migrateTo(DataStore) để thực hiện quá trình di chuyển.

Bạn có thể sử dụng DataStoreCredentialRefreshListener và đặt thông tin xác thực bằng GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).

Quy trình sử dụng mã uỷ quyền

Sử dụng quy trình mã uỷ quyền để cho phép người dùng cuối cấp cho ứng dụng của bạn quyền truy cập vào dữ liệu được bảo vệ của họ trên các API của Google. Giao thức cho quy trình này được chỉ định trong Cấp mã uỷ quyền.

Quy trình này được triển khai bằng GoogleAuthorizationCodeFlow. Các bước thực hiện:

  • Người dùng cuối đăng nhập vào ứng dụng của bạn. Bạn sẽ cần liên kết người dùng đó với một mã nhận dạng người dùng riêng biệt cho ứng dụng của mình.
  • Gọi AuthorizationCodeFlow.loadCredential(String)) dựa trên mã nhận dạng người dùng để kiểm tra xem thông tin đăng nhập của người dùng cuối đã được biết hay chưa. Nếu có, vậy là xong.
  • Nếu không, hãy gọi AuthorizationCodeFlow.newAuthorizationUrl() và chuyển hướng trình duyệt của người dùng cuối đến một trang uỷ quyền để cấp cho ứng dụng của bạn quyền truy cập vào dữ liệu được bảo vệ của họ.
  • Sau đó, máy chủ uỷ quyền của Google sẽ chuyển hướng trình duyệt trở lại URL chuyển hướng do ứng dụng của bạn chỉ định, cùng với tham số truy vấn code. Dùng tham số code để yêu cầu mã truy cập bằng AuthorizationCodeFlow.newTokenRequest(String)).
  • Sử dụng AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String)) để lưu trữ và lấy thông tin đăng nhập để truy cập vào các tài nguyên được bảo vệ.

Ngoài ra, nếu không sử dụng GoogleAuthorizationCodeFlow, bạn có thể sử dụng các lớp cấp thấp hơn:

Khi thiết lập dự án trong Google API Console, bạn sẽ chọn trong số các thông tin xác thực khác nhau, tuỳ thuộc vào quy trình bạn đang sử dụng. Để biết thêm thông tin, hãy xem phần Thiết lập OAuth 2.0Các trường hợp sử dụng OAuth 2.0. Dưới đây là các đoạn mã cho từng luồng.

Ứng dụng máy chủ web

Giao thức cho quy trình này được giải thích trong phần Sử dụng OAuth 2.0 cho ứng dụng máy chủ web.

Thư viện này cung cấp các lớp trợ giúp servlet để đơn giản hoá đáng kể quy trình mã uỷ quyền cho các trường hợp sử dụng cơ bản. Bạn chỉ cần cung cấp các lớp con cụ thể của AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (từ google-oauth-client-servlet) rồi thêm chúng vào tệp web.xml. Xin lưu ý rằng bạn vẫn cần xử lý việc đăng nhập của người dùng cho ứng dụng web và trích xuất một mã nhận dạng người dùng.

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
  }
}

Các ứng dụng Google App Engine

Quy trình mã uỷ quyền trên App Engine gần giống với quy trình mã uỷ quyền servlet, ngoại trừ việc chúng ta có thể tận dụng API Người dùng Java của Google App Engine. Người dùng cần đăng nhập để bật Users Java API; để biết thông tin về cách chuyển hướng người dùng đến trang đăng nhập nếu họ chưa đăng nhập, hãy xem phần Bảo mật và xác thực (trong web.xml).

Điểm khác biệt chính so với trường hợp servlet là bạn cung cấp các lớp con cụ thể của AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (từ google-oauth-client-appengine. Các lớp này mở rộng các lớp servlet trừu tượng và triển khai phương thức getUserId cho bạn bằng cách sử dụng Users Java API. AppEngineDataStoreFactory (từ google-http-client-appengine) là một lựa chọn phù hợp để duy trì thông tin đăng nhập bằng API DataStore của Google App Engine.

Ví dụ được lấy (có sửa đổi một chút) từ 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();
  }
}

Để xem một mẫu khác, hãy xem storage-serviceaccount-appengine-sample.

Tài khoản dịch vụ

GoogleCredential cũng hỗ trợ tài khoản dịch vụ. Không giống như thông tin đăng nhập mà một ứng dụng khách yêu cầu quyền truy cập vào dữ liệu của người dùng cuối, Tài khoản dịch vụ cung cấp quyền truy cập vào dữ liệu riêng của ứng dụng khách. Ứng dụng khách của bạn ký yêu cầu mã thông báo truy cập bằng khoá riêng tư được tải xuống từ Google API Console.

Ví dụ về cách sử dụng:

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();
...

Để xem thêm một mẫu, hãy xem storage-serviceaccount-cmdline-sample.

Mạo danh

Bạn cũng có thể sử dụng quy trình tài khoản dịch vụ để mạo danh một người dùng trong miền mà bạn sở hữu. Quy trình này rất giống với quy trình tài khoản dịch vụ ở trên, nhưng bạn gọi thêm GoogleCredential.Builder.setServiceAccountUser(String).

Các ứng dụng đã cài đặt

Đây là quy trình mã uỷ quyền dòng lệnh được mô tả trong phần Sử dụng OAuth 2.0 cho các ứng dụng đã cài đặt.

Ví dụ về cách sử dụng:

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");
}

Ứng dụng phía máy khách

Để sử dụng quy trình ứng dụng dựa trên trình duyệt như mô tả trong phần Sử dụng OAuth 2.0 cho các ứng dụng phía máy khách, bạn thường sẽ làm theo các bước sau:

  1. Chuyển hướng người dùng cuối trong trình duyệt đến trang uỷ quyền bằng cách sử dụng GoogleBrowserClientRequestUrl để cấp cho ứng dụng trình duyệt của bạn quyền truy cập vào dữ liệu được bảo vệ của người dùng cuối.
  2. Sử dụng Thư viện ứng dụng API của Google cho JavaScript để xử lý mã truy cập có trong đoạn URL tại URI chuyển hướng đã đăng ký tại Google API Console.

Ví dụ về cách sử dụng cho một ứng dụng web:

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

Thư viện nào sẽ dùng với Android:

Nếu bạn đang phát triển cho Android và API Google mà bạn muốn sử dụng có trong Thư viện Dịch vụ Google Play, hãy sử dụng thư viện đó để có hiệu suất và trải nghiệm tốt nhất. Nếu API của Google mà bạn muốn dùng với Android không thuộc thư viện Dịch vụ Google Play, thì bạn có thể dùng Thư viện ứng dụng Google API cho Java. Thư viện này hỗ trợ Android 4.0 (Ice Cream Sandwich) (trở lên) và được mô tả tại đây. Thư viện ứng dụng Google API cho Java hỗ trợ Android ở phiên bản @Beta.

Thông tin cơ bản:

Bắt đầu từ Eclair (SDK 2.1), tài khoản người dùng được quản lý trên thiết bị Android bằng Trình quản lý tài khoản. Tất cả hoạt động uỷ quyền ứng dụng Android đều do SDK quản lý tập trung bằng AccountManager. Bạn chỉ định phạm vi OAuth 2.0 mà ứng dụng của bạn cần và ứng dụng sẽ trả về một mã truy cập để sử dụng.

Phạm vi OAuth 2.0 được chỉ định thông qua tham số authTokenType dưới dạng oauth2: cộng với phạm vi. Ví dụ:

oauth2:https://www.googleapis.com/auth/tasks

Tham số này chỉ định quyền đọc/ghi đối với Google Tasks API. Nếu bạn cần nhiều phạm vi OAuth 2.0, hãy sử dụng danh sách được phân tách bằng dấu cách.

Một số API có các tham số authTokenType đặc biệt cũng hoạt động. Ví dụ: "Quản lý việc cần làm" là một bí danh cho ví dụ authtokenType được minh hoạ ở trên.

Bạn cũng phải chỉ định khoá API trong Google API Console. Nếu không, mã thông báo mà AccountManager cung cấp cho bạn sẽ chỉ cung cấp cho bạn hạn mức ẩn danh, thường rất thấp. Ngược lại, khi chỉ định một khoá API, bạn sẽ nhận được hạn mức miễn phí cao hơn và có thể tuỳ ý thiết lập tính phí cho mức sử dụng vượt quá hạn mức đó.

Đoạn mã ví dụ lấy từ 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;
  }
}