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 GoogleCredential tiện ích để thực hiện uỷ quyền OAuth 2.0 với các dịch vụ của Google. Cho thông tin về các chức năng 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 OAuth của Google cho Java.

Tóm tắt: Để truy cập vào dữ liệu được bảo vệ được lưu trữ trên các dịch vụ của Google, hãy sử dụng OAuth 2.0 để uỷ quyền. Google API hỗ trợ quy trình OAuth 2.0 cho nhiều loại ứng dụng. Trong tất cả các quy trình này, ứng dụng khách đều 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ệ truy cập. Mã truy cập cũng được liên kết với một phạm vi giới hạn xác định loại dữ liệu mà ứng dụng của bạn có quyền truy cập (ví dụ: "Quản lý việc bạn cần làm"). Mục tiêu quan trọng đối với OAuth 2.0 là cung cấp tính bảo mật và truy cập 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 API của Google dành cho Java được xây dựng dựa trên mục đích chung Thư viện ứng dụng Google OAuth 2.0 cho Java.

Để 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

Trước khi có thể truy cập các API của Google, bạn cần thiết lập một dự án trên Bảng điều khiển API của Google để xác thực và thanh toán cho dù ứng dụng của bạn là một ứng dụng đã được cài đặt, ứng dụng dành cho thiết bị di động, máy chủ web hoặc ứng dụng khách 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ề Bảng điều khiển API.

Thông tin xác thực

GoogleCredential

GoogleCredential là lớp trợ giúp an toàn cho luồng cho 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();

Thông tin nhận dạng Google App Engine

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

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 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à mình 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ã thông báo truy cập thường có ngày hết hạn là 1 giờ, sau ngày này bạn sẽ sẽ gặp lỗi nếu bạn cố gắng sử dụng công cụ đó. GoogleCredential tự động "làm mới" mã thông báo, chỉ đơn giản là việc nhận được một mã truy cập mới. Việc này được thực hiện thông qua mã làm mới tồn tại trong một thời gian dài 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 gửi 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 của thông tin đăng nhập và/hoặc mã làm mới. Để 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 cách triển khai DataStoreFactory của riêng bạn bằng StoredCredential; hoặc có thể dùng một trong các cách triển khai sau đây do thư viện cung cấp:

  • AppEngineDataStoreFactory: duy trì thông tin đăng nhập bằng cách sử dụng API Google App Engine Data Store.
  • MemoryDataStoreFactory: "bền vững" thông tin đăng nhập trong bộ nhớ. Thông tin này chỉ hữu ích dưới dạng bộ nhớ ngắn hạn trong suốt vòng đời của quá trình.
  • FileDataStoreFactory: vẫn giữ nguyên thông tin đăng nhập trong một tệp.

Người dùng AppEngine: AppEngineCredentialStore không được dùng nữa và sẽ sớm bị xoá. Bạn nên sử dụng AppEngineDataStoreFactory bằng StoredCredential. Nếu có thông tin đăng nhập được lưu trữ theo kiểu cũ, bạn có thể sử dụng các phương thức trợ giúp 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 mã này cho 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 ứ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 của luồng này là đượ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 đó bằng một mã nhận dạng người dùng dành riêng cho ứng dụng của bạn.
  • Gọi AuthorizationCodeFlow.loadCredential(String)) dựa trên mã nhận dạng người dùng để kiểm tra xem liệu thông tin xác thực của người dùng cuối đã được biết hay chưa. Nếu có, chúng tôi đã hoàn tất.
  • 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 trang uỷ quyền để cấp quyền truy cập của ứng dụng 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 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 LicenseCodeFlow.createAndStoreCredential(TokenResponse, Chuỗi)) để lưu trữ và lấy thông tin xác thực để truy cập các tài nguyên được bảo vệ.

Ngoài ra, nếu hiện 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, mà bạn chọn trong số các thông tin đăng nhập khác nhau, tuỳ thuộc vào quy trình bạn đang sử dụng. Để biết thêm chi tiết, hãy xem bài viết Thiết lập OAuth 2.0Tình huống OAuth 2.0. Dưới đây là đoạn mã cho từng flow.

Ứng dụng máy chủ web

Giao thức của luồng này được giải thích trong 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ình 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ỉ cung cấp các lớp con cụ thể của AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (từ google-oauth-client-servlet) và thêm chúng vào tệp web.xml của bạn. Xin lưu ý rằng bạn vẫn cần quan tâm đến vấn đề người dùng đăng nhập cho ứng dụng web của bạn và trích xuấ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
  }
}

Ứng dụng Google App Engine

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

Sự khác biệt chính so với trường hợp servlet là bạn cung cấp dữ liệu cụ thể lớp con của AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (từ google-oauth-client-appengine. Chúng 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 API Java của người dùng. 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 cách sử dụng Dữ liệu Google App Engine Store API.

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 thêm mẫu, 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à trong đó ứng dụng khách yêu cầu quyền truy cập vào một dữ liệu của người dùng cuối, thì Tài khoản dịch vụ sẽ cung cấp quyền truy cập vào dữ liệu của riêng bạn. Ứng dụng khách của bạn ký yêu cầu cấp mã truy cập bằng cách sử dụng khoá riêng tư mà bạn đã tải xuống từ Google API Console.

Mã mẫu lấy từ 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();
...

Để xem thêm 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ụ để nhập vai người dùng trong miền mà bạn sở hữu. Việc này rất giống với quy trình dành cho tài khoản dịch vụ ở trên, nhưng bạn hãy 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 bài viết Sử dụng OAuth 2.0 cho ứng dụng đã cài đặt.

Đoạn mã mẫu từ 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");
}

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

Để sử dụng luồng ứng dụng dựa trên trình duyệt được mô tả trong Sử dụng OAuth 2.0 cho ứng dụng phía máy khách, bạn thường 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. Dùng Thư viện ứng dụng API của Google dành cho JavaScript để xử lý mã truy cập có trong phân đ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 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

@Thử nghiệm

Sử dụng thư viện nào với Android:

Nếu bạn đang phát triển cho Android và API của Google mà bạn muốn sử dụng được bao gồm trong Thư viện Dịch vụ Google Play, 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, bạn muốn sử dụng với Android không thuộc thư viện Dịch vụ Google Play, bạn có thể sử dụng Thư viện ứng dụng API của Google dành cho Java, hỗ trợ Android 4.0 (Ice Cream Sandwich) (hoặc cao hơn) và được mô tả ở đây. Hỗ trợ cho Android trong Google Thư viện ứng dụng API cho Java là @Beta.

Thông tin cơ bản:

Kể từ Eclair (SDK 2.1), tài khoản người dùng được quản lý trên một thiết bị Android thông qua Người quản lý tài khoản. Tất cả việc uỷ quyền ứng dụng Android đều được tập trung do SDK quản lý bằng cách sử dụ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 này trả về quyền truy cập mã thông báo để 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

Thao tác 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, sử dụng danh sách được phân tách bằng dấu cách.

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

Bạn cũng phải chỉ định khoá API từ Bảng điều khiển API của Google. Nếu không, mã thông báo mà AccountManager cung cấp cho bạn chỉ cung cấp cho bạn hạn mức ẩn danh thường rất thấp. Ngược lại, bằng cách chỉ định 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 thông tin thanh toán cho việc sử dụng ở trên đó.

Đoạn mã mẫu được 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;
  }
}