Xác thực bằng máy chủ phụ trợ

Nếu sử dụng tính năng Đăng nhập bằng Google thông qua một ứng dụng hoặc trang web giao tiếp với một máy chủ phụ trợ, thì bạn có thể cần xác định người dùng đang đăng nhập trên máy chủ đó. Để thực hiện việc này một cách an toàn, sau khi người dùng đăng nhập thành công, hãy gửi mã thông báo mã nhận dạng của người dùng đó đến máy chủ của bạn bằng HTTPS. Sau đó, trên máy chủ, hãy xác minh tính toàn vẹn của mã thông báo mã nhận dạng và sử dụng thông tin người dùng có trong mã thông báo để thiết lập phiên hoặc tạo tài khoản mới.

Gửi mã thông báo nhận dạng đến máy chủ của bạn

Trước tiên, khi người dùng đăng nhập, hãy lấy mã nhận dạng của họ:

  1. Khi bạn định cấu hình tính năng Đăng nhập bằng Google, hãy gọi phương thức requestIdToken và truyền phương thức này mã ứng dụng khách web của máy chủ.

    // Request only the user's ID token, which can be used to identify the
    // user securely to your backend. This will contain the user's basic
    // profile (name, profile picture URL, etc) so you should not need to
    // make an additional call to personalize your application.
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.server_client_id))
            .requestEmail()
            .build();
  2. Khi ứng dụng khởi động, hãy kiểm tra xem người dùng đã đăng nhập vào ứng dụng của bạn bằng Google trên thiết bị này hoặc thiết bị khác hay chưa bằng cách gọi silentSignIn:

    GoogleSignIn.silentSignIn()
        .addOnCompleteListener(
            this,
            new OnCompleteListener<GoogleSignInAccount>() {
              @Override
              public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                handleSignInResult(task);
              }
            });
  3. Nếu người dùng không thể tự đăng nhập, hãy thể hiện trải nghiệm đăng xuất thông thường của bạn và cung cấp cho người dùng lựa chọn đăng nhập. Khi người dùng đăng nhập, hãy lấy GoogleSignInAccount của người dùng trong kết quả hoạt động của ý định đăng nhập:

    // This task is always completed immediately, there is no need to attach an
    // asynchronous listener.
    Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
    handleSignInResult(task);
  4. Sau khi người dùng đăng nhập ngầm hoặc rõ ràng, hãy lấy mã thông báo mã nhận dạng từ đối tượng GoogleSignInAccount:

    private void handleSignInResult(@NonNull Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount account = completedTask.getResult(ApiException.class);
            String idToken = account.getIdToken();
    
            // TODO(developer): send ID Token to server and validate
    
            updateUI(account);
        } catch (ApiException e) {
            Log.w(TAG, "handleSignInResult:error", e);
            updateUI(null);
        }
    }

Sau đó, gửi mã thông báo ID đến máy chủ của bạn với yêu cầu POST qua HTTPS:

HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost("https://yourbackend.example.com/tokensignin");

try {
  List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
  nameValuePairs.add(new BasicNameValuePair("idToken", idToken));
  httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

  HttpResponse response = httpClient.execute(httpPost);
  int statusCode = response.getStatusLine().getStatusCode();
  final String responseBody = EntityUtils.toString(response.getEntity());
  Log.i(TAG, "Signed in as: " + responseBody);
} catch (ClientProtocolException e) {
  Log.e(TAG, "Error sending ID token to backend.", e);
} catch (IOException e) {
  Log.e(TAG, "Error sending ID token to backend.", e);
}

Xác minh tính toàn vẹn của mã thông báo giá trị nhận dạng

Sau khi nhận được mã thông báo mã nhận dạng qua HTTPS POST, bạn phải xác minh tính toàn vẹn của mã thông báo.

Để xác minh rằng mã thông báo là hợp lệ, hãy đảm bảo đáp ứng các tiêu chí sau:

  • Google đã ký mã thông báo nhận dạng đúng cách. Bạn có thể dùng khoá công khai của Google (có sẵn ở định dạng JWK hoặc PEM) để xác minh chữ ký của mã thông báo. Các khoá này thường xuyên được xoay vòng; hãy kiểm tra tiêu đề Cache-Control trong phản hồi để xác định thời điểm bạn nên truy xuất lại các khoá.
  • Giá trị của aud trong mã thông báo mã nhận dạng bằng với một trong các mã ứng dụng khách của ứng dụng. Quy trình kiểm tra này là cần thiết để ngăn chặn hành vi dùng mã thông báo mã nhận dạng được cấp cho một ứng dụng độc hại để truy cập vào dữ liệu về cùng một người dùng trên máy chủ phụ trợ của ứng dụng.
  • Giá trị của iss trong mã thông báo mã nhận dạng bằng accounts.google.com hoặc https://accounts.google.com.
  • Chưa qua thời gian hết hạn (exp) của mã thông báo giá trị nhận dạng.
  • Nếu cần xác thực rằng mã thông báo nhận dạng đại diện cho tài khoản Google Workspace hoặc tài khoản tổ chức Cloud, bạn có thể kiểm tra thông báo xác nhận quyền sở hữu hd, trong đó cho biết miền được lưu trữ của người dùng. Bạn phải dùng tính năng này khi chỉ cho phép các thành viên của một số miền nhất định truy cập vào tài nguyên. Việc không có xác nhận quyền sở hữu này cho thấy tài khoản không thuộc một miền do Google lưu trữ.

Thay vì tự viết mã để thực hiện các bước xác minh này, bạn nên sử dụng thư viện ứng dụng API của Google cho nền tảng của mình hoặc thư viện JWT đa năng. Để phát triển và gỡ lỗi, bạn có thể gọi điểm cuối xác thực tokeninfo.

Sử dụng Thư viện ứng dụng API của Google

Bạn nên sử dụng một trong các Thư viện ứng dụng API của Google (ví dụ: Java, Node.js, PHP, Python) để xác thực mã thông báo mã nhận dạng của Google trong môi trường phát hành công khai.

Java

Để xác thực mã thông báo mã nhận dạng trong Java, hãy sử dụng đối tượng GoogleIdTokenVerifier. Ví dụ:

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;

...

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
    // Specify the CLIENT_ID of the app that accesses the backend:
    .setAudience(Collections.singletonList(CLIENT_ID))
    // Or, if multiple clients access the backend:
    //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
    .build();

// (Receive idTokenString by HTTPS POST)

GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
  Payload payload = idToken.getPayload();

  // Print user identifier
  String userId = payload.getSubject();
  System.out.println("User ID: " + userId);

  // Get profile information from payload
  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");

  // Use or store profile information
  // ...

} else {
  System.out.println("Invalid ID token.");
}

Phương thức GoogleIdTokenVerifier.verify() xác minh chữ ký JWT, thông báo xác nhận quyền sở hữu aud, thông báo xác nhận quyền sở hữu iss và thông báo xác nhận quyền sở hữu exp.

Nếu cần xác thực rằng mã thông báo nhận dạng đại diện cho tài khoản tổ chức Google Workspace hoặc Cloud, bạn có thể xác minh quyền sở hữu hd bằng cách kiểm tra tên miền mà phương thức Payload.getHostedDomain() trả về. Miền trong thông báo xác nhận quyền sở hữu email không đủ để đảm bảo rằng tài khoản do một miền hoặc tổ chức quản lý.

Node.js

Để xác thực mã thông báo mã nhận dạng trong Node.js, hãy sử dụng Thư viện xác thực của Google cho Node.js. Cài đặt thư viện:

npm install google-auth-library --save
Sau đó, hãy gọi hàm verifyIdToken(). Ví dụ:

const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client();
async function verify() {
  const ticket = await client.verifyIdToken({
      idToken: token,
      audience: CLIENT_ID,  // Specify the CLIENT_ID of the app that accesses the backend
      // Or, if multiple clients access the backend:
      //[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
  });
  const payload = ticket.getPayload();
  const userid = payload['sub'];
  // If the request specified a Google Workspace domain:
  // const domain = payload['hd'];
}
verify().catch(console.error);

Hàm verifyIdToken xác minh chữ ký JWT, thông báo xác nhận quyền sở hữu aud, thông báo xác nhận quyền sở hữu exp và thông báo xác nhận quyền sở hữu iss.

Nếu cần xác thực rằng mã thông báo nhận dạng đại diện cho tài khoản Google Workspace hoặc tài khoản tổ chức Cloud, bạn có thể kiểm tra thông báo xác nhận quyền sở hữu hd, trong đó cho biết miền được lưu trữ của người dùng. Bạn phải sử dụng tính năng này khi chỉ cho phép các thành viên của một số miền truy cập vào tài nguyên. Nếu không có thông báo xác nhận quyền sở hữu này, thì tài khoản đó không thuộc một miền do Google lưu trữ.

PHP

Để xác thực mã thông báo mã nhận dạng trong PHP, hãy dùng Thư viện ứng dụng Google API cho PHP. Cài đặt thư viện (ví dụ: bằng Composer):

composer require google/apiclient
Sau đó, hãy gọi hàm verifyIdToken(). Ví dụ:

require_once 'vendor/autoload.php';

// Get $id_token via HTTPS POST.

$client = new Google_Client(['client_id' => $CLIENT_ID]);  // Specify the CLIENT_ID of the app that accesses the backend
$payload = $client->verifyIdToken($id_token);
if ($payload) {
  $userid = $payload['sub'];
  // If the request specified a Google Workspace domain
  //$domain = $payload['hd'];
} else {
  // Invalid ID token
}

Hàm verifyIdToken xác minh chữ ký JWT, thông báo xác nhận quyền sở hữu aud, thông báo xác nhận quyền sở hữu exp và thông báo xác nhận quyền sở hữu iss.

Nếu cần xác thực rằng mã thông báo nhận dạng đại diện cho tài khoản Google Workspace hoặc tài khoản tổ chức Cloud, bạn có thể kiểm tra thông báo xác nhận quyền sở hữu hd, trong đó cho biết miền được lưu trữ của người dùng. Bạn phải sử dụng tính năng này khi chỉ cho phép các thành viên của một số miền truy cập vào tài nguyên. Nếu không có thông báo xác nhận quyền sở hữu này, thì tài khoản đó không thuộc một miền do Google lưu trữ.

Python

Để xác thực mã thông báo giá trị nhận dạng trong Python, hãy dùng hàm verify_oauth2_token. Ví dụ:

from google.oauth2 import id_token
from google.auth.transport import requests

# (Receive token by HTTPS POST)
# ...

try:
    # Specify the CLIENT_ID of the app that accesses the backend:
    idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)

    # Or, if multiple clients access the backend server:
    # idinfo = id_token.verify_oauth2_token(token, requests.Request())
    # if idinfo['aud'] not in [CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]:
    #     raise ValueError('Could not verify audience.')

    # If the request specified a Google Workspace domain
    # if idinfo['hd'] != DOMAIN_NAME:
    #     raise ValueError('Wrong domain name.')

    # ID token is valid. Get the user's Google Account ID from the decoded token.
    userid = idinfo['sub']
except ValueError:
    # Invalid token
    pass

Hàm verify_oauth2_token xác minh chữ ký JWT, thông báo xác nhận quyền sở hữu aud và thông báo xác nhận quyền sở hữu exp. Bạn cũng phải xác minh thông báo xác nhận quyền sở hữu hd (nếu có) bằng cách kiểm tra đối tượng mà verify_oauth2_token trả về. Nếu có nhiều ứng dụng truy cập vào máy chủ phụ trợ, hãy xác minh thông báo xác nhận quyền sở hữu aud theo cách thủ công.

调用 tokeninfo 端点

若要验证用于调试的 ID 令牌签名,一种简单的方法是使用 tokeninfo 端点。调用此端点涉及到一个额外的网络请求,该网络请求会为您执行大部分验证,而您在自己的代码中测试适当的验证和载荷提取时。它不适合在生产代码中使用,因为请求可能会受到限制或出现间歇性错误。

如需使用 tokeninfo 端点验证 ID 令牌,请向该端点发出 HTTPS POST 或 GET 请求,并在 id_token 参数中传递您的 ID 令牌。例如,要验证令牌“XYZ123”,可发出以下 GET 请求:

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

如果令牌已正确签名,并且 issexp 声明具有预期值,您会收到 HTTP 200 响应,其正文包含 JSON 格式的 ID 令牌声明。以下是示例响应:

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

如果您需要验证此 ID 令牌是否代表 Google Workspace 帐号,可以检查 hd 声明,该声明指示用户的托管网域。仅允许特定网域的成员访问资源时,必须使用此方法。缺少此声明表示帐号不属于 Google Workspace 托管网域。

Tạo tài khoản hoặc phiên

Sau khi bạn xác minh mã thông báo, hãy kiểm tra xem người dùng đã có trong cơ sở dữ liệu người dùng của bạn hay chưa. Nếu có, hãy thiết lập một phiên được xác thực cho người dùng. Nếu người dùng chưa có trong cơ sở dữ liệu người dùng của bạn, hãy tạo một bản ghi người dùng mới từ thông tin trong tải trọng của mã thông báo mã nhận dạng và thiết lập một phiên cho người dùng đó. Bạn có thể nhắc người dùng về mọi thông tin hồ sơ bổ sung mà bạn yêu cầu khi phát hiện một người dùng mới được tạo trong ứng dụng của mình.

Bảo mật tài khoản của người dùng bằng tính năng Bảo vệ nhiều tài khoản

Khi sử dụng Google để đăng nhập cho người dùng, bạn sẽ tự động được hưởng lợi từ tất cả cơ sở hạ tầng và tính năng bảo mật mà Google xây dựng để bảo vệ dữ liệu của người dùng. Tuy nhiên, trong trường hợp hiếm gặp là Tài khoản Google của người dùng bị xâm phạm hoặc có một số sự kiện bảo mật quan trọng khác, ứng dụng của bạn cũng có thể dễ bị tấn công. Để bảo vệ tài khoản của bạn hiệu quả hơn trước mọi sự kiện bảo mật lớn, hãy sử dụng tính năng Bảo vệ nhiều tài khoản để nhận cảnh báo bảo mật từ Google. Khi nhận được những sự kiện này, bạn nắm được các thay đổi quan trọng đối với tính bảo mật cho Tài khoản Google của người dùng, sau đó, bạn có thể thực hiện hành động đối với dịch vụ để bảo mật tài khoản của mình.