Tính năng Đăng nhập bằng Google cho các ứng dụng phía máy chủ

Để thay mặt người dùng sử dụng các dịch vụ của Google khi người dùng không có kết nối mạng, bạn phải sử dụng quy trình phía máy chủ kết hợp, theo đó người dùng uỷ quyền cho ứng dụng của bạn ở phía máy khách bằng ứng dụng API JavaScript, đồng thời bạn gửi mã uỷ quyền một lần đặc biệt đến máy chủ của mình. Máy chủ của bạn sẽ trao đổi mã dùng một lần này để lấy mã truy cập và làm mới mã riêng từ Google để máy chủ có thể thực hiện lệnh gọi API riêng mà người dùng không có kết nối mạng. Quy trình gửi mã một lần này có những ưu điểm về bảo mật so với cả quy trình thuần tuý phía máy chủ lẫn quy trình gửi mã truy cập đến máy chủ của bạn.

Quy trình đăng nhập để lấy mã truy cập cho ứng dụng phía máy chủ được minh hoạ bên dưới.

Mã một lần có một số ưu điểm về bảo mật. Khi sử dụng mã, Google cung cấp mã thông báo trực tiếp cho máy chủ của bạn mà không cần qua bên trung gian nào. Mặc dù bạn không nên để lộ mã bị rò rỉ, nhưng các mã này rất khó sử dụng nếu không có bí mật ứng dụng khách của bạn. Hãy giữ bí mật cho khách hàng của bạn!

Triển khai quy trình mã một lần

Nút Đăng nhập bằng Google cung cấp cả mã truy cậpmã uỷ quyền. Mã này là mã một lần mà máy chủ của bạn có thể trao đổi với máy chủ của Google để lấy mã truy cập.

Mã mẫu sau đây minh hoạ cách thực hiện quy trình mã một lần.

Để xác thực quy trình Đăng nhập bằng Google bằng mã một lần, bạn cần phải:

Bước 1: Tạo mã ứng dụng khách và mật khẩu ứng dụng khách

Để tạo mã ứng dụng khách và mật khẩu ứng dụng khách, hãy tạo một dự án Google API Console, thiết lập mã ứng dụng khách OAuth và đăng ký nguồn gốc JavaScript của bạn:

  1. Chuyển đến Google API Console.

  2. Trên trình đơn thả xuống của dự án, hãy chọn một dự án hiện có hoặc tạo dự án mới bằng cách chọn Create a new project (Tạo dự án mới).

  3. Trên thanh bên trong phần "API và dịch vụ", hãy chọn Thông tin xác thực, rồi nhấp vào Định cấu hình màn hình đồng ý.

    Chọn một Địa chỉ email, chỉ định Tên sản phẩm rồi nhấn vào Lưu.

  4. Trong thẻ Thông tin xác thực, hãy chọn danh sách thả xuống Tạo thông tin xác thực rồi chọn Mã ứng dụng khách OAuth.

  5. Trong Application type (Loại ứng dụng), hãy chọn Web application (Ứng dụng web).

    Đăng ký những nguồn gốc mà ứng dụng của bạn được phép truy cập vào các API của Google như sau. Nguồn gốc là sự kết hợp duy nhất của giao thức, tên máy chủ và cổng.

    1. Trong trường Nguồn gốc JavaScript được cho phép, hãy nhập nguồn gốc cho ứng dụng của bạn. Bạn có thể nhập nhiều nguồn gốc để cho phép ứng dụng chạy trên các giao thức, miền hoặc miền con khác nhau. Bạn không thể sử dụng ký tự đại diện. Trong ví dụ bên dưới, URL thứ hai có thể là một URL sản xuất.

      http://localhost:8080
      https://myproductionurl.example.com
      
    2. Trường URI chuyển hướng được ủy quyền không yêu cầu giá trị. URI chuyển hướng không được sử dụng với API JavaScript.

    3. Nhấn vào nút Create (Tạo).

  6. Từ hộp thoại ứng dụng OAuth nhận được, hãy sao chép mã ứng dụng. Mã ứng dụng khách cho phép ứng dụng của bạn truy cập vào các API đã bật của Google.

Bước 2: Đưa thư viện Nền tảng của Google vào trang của bạn

Bao gồm các tập lệnh sau minh hoạ một hàm ẩn danh có chức năng chèn tập lệnh vào DOM của trang web index.html này.

<!-- The top of file index.html -->
<html itemscope itemtype="http://schema.org/Article">
<head>
  <!-- BEGIN Pre-requisites -->
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
  </script>
  <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer>
  </script>
  <!-- END Pre-requisites -->

Bước 3: Khởi tạo đối tượng GoogleAuth

Tải thư viện auth2 và gọi gapi.auth2.init() để khởi chạy đối tượng GoogleAuth. Chỉ định mã ứng dụng khách và phạm vi bạn muốn yêu cầu khi gọi init().

<!-- Continuing the <head> section -->
  <script>
    function start() {
      gapi.load('auth2', function() {
        auth2 = gapi.auth2.init({
          client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
          // Scopes to request in addition to 'profile' and 'email'
          //scope: 'additional_scope'
        });
      });
    }
  </script>
</head>
<body>
  <!-- ... -->
</body>
</html>

Bước 4: Thêm nút đăng nhập vào trang của bạn

Thêm nút đăng nhập vào trang web của bạn và đính kèm trình xử lý lượt nhấp để gọi grantOfflineAccess() nhằm bắt đầu quy trình mã một lần.

<!-- Add where you want your sign-in button to render -->
<!-- Use an image that follows the branding guidelines in a real app -->
<button id="signinButton">Sign in with Google</button>
<script>
  $('#signinButton').click(function() {
    // signInCallback defined in step 6.
    auth2.grantOfflineAccess().then(signInCallback);
  });
</script>

Bước 5: Đăng nhập bằng tài khoản

Người dùng nhấp vào nút đăng nhập và cấp cho ứng dụng của bạn quyền truy cập vào các quyền mà bạn đã yêu cầu. Sau đó, hàm callback mà bạn đã chỉ định trong phương thức grantOfflineAccess().then() sẽ được chuyển một đối tượng JSON có mã uỷ quyền. Ví dụ:

{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}

Bước 6: Gửi mã uỷ quyền đến máy chủ

code là mã một lần mà máy chủ của bạn có thể đổi lấy mã truy cập và mã làm mới của riêng nó. Bạn chỉ có thể lấy mã làm mới sau khi người dùng thấy hộp thoại uỷ quyền yêu cầu quyền truy cập ngoại tuyến. Nếu đã chỉ định select-account prompt trong OfflineAccessOptions ở bước 4, bạn phải lưu trữ mã làm mới mà bạn truy xuất để sử dụng sau này, vì các lần trao đổi tiếp theo sẽ trả về null cho mã làm mới. Quy trình này có khả năng bảo mật cao hơn so với quy trình OAuth 2.0 tiêu chuẩn.

Mã truy cập luôn được trả về bằng cách trao đổi mã uỷ quyền hợp lệ.

Tập lệnh sau đây xác định hàm callback cho nút đăng nhập. Khi đăng nhập thành công, hàm sẽ lưu trữ mã truy cập để sử dụng phía máy khách và gửi mã một lần đến máy chủ của bạn trên cùng một miền.

<!-- Last part of BODY element in file index.html -->
<script>
function signInCallback(authResult) {
  if (authResult['code']) {

    // Hide the sign-in button now that the user is authorized, for example:
    $('#signinButton').attr('style', 'display: none');

    // Send the code to the server
    $.ajax({
      type: 'POST',
      url: 'http://example.com/storeauthcode',
      // Always include an `X-Requested-With` header in every AJAX request,
      // to protect against CSRF attacks.
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      },
      contentType: 'application/octet-stream; charset=utf-8',
      success: function(result) {
        // Handle or verify the server response.
      },
      processData: false,
      data: authResult['code']
    });
  } else {
    // There was an error.
  }
}
</script>

Bước 7: Đổi mã uỷ quyền lấy mã truy cập

Trên máy chủ, hãy đổi mã xác thực để lấy quyền truy cập và làm mới mã thông báo. Hãy sử dụng mã truy cập để gọi các API của Google thay mặt cho người dùng và tuỳ ý lưu trữ mã làm mới để lấy mã truy cập mới khi mã truy cập hết hạn.

Nếu đã yêu cầu quyền truy cập vào hồ sơ, bạn cũng sẽ nhận được mã thông báo mã nhận dạng chứa thông tin cơ bản về hồ sơ của người dùng.

Ví dụ:

Java
// (Receive authCode via HTTPS POST)


if (request.getHeader("X-Requested-With") == null) {
  // Without the `X-Requested-With` header, this request could be forged. Aborts.
}

// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.cloud.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";

// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
    GoogleClientSecrets.load(
        JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
          new GoogleAuthorizationCodeTokenRequest(
              new NetHttpTransport(),
              JacksonFactory.getDefaultInstance(),
              "https://oauth2.googleapis.com/token",
              clientSecrets.getDetails().getClientId(),
              clientSecrets.getDetails().getClientSecret(),
              authCode,
              REDIRECT_URI)  // Specify the same redirect URI that you use with your web
                             // app. If you don't have a web version of your app, you can
                             // specify an empty string.
              .execute();

String accessToken = tokenResponse.getAccessToken();

// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
    new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
        .setApplicationName("Auth Code Exchange Demo")
        .build();
File file = drive.files().get("appfolder").execute();

// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject();  // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
Python
from apiclient import discovery
import httplib2
from oauth2client import client

# (Receive auth_code by HTTPS POST)


# If this request does not have `X-Requested-With` header, this could be a CSRF
if not request.headers.get('X-Requested-With'):
    abort(403)

# Set path to the Web application client_secret_*.json file you downloaded from the
# Google API Console: https://console.cloud.google.com/apis/credentials
CLIENT_SECRET_FILE = '/path/to/client_secret.json'

# Exchange auth code for access token, refresh token, and ID token
credentials = client.credentials_from_clientsecrets_and_code(
    CLIENT_SECRET_FILE,
    ['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
    auth_code)

# Call Google API
http_auth = credentials.authorize(httplib2.Http())
drive_service = discovery.build('drive', 'v3', http=http_auth)
appfolder = drive_service.files().get(fileId='appfolder').execute()

# Get profile info from ID token
userid = credentials.id_token['sub']
email = credentials.id_token['email']