Xác thực các lệnh gọi lại xác thực phía máy chủ (SSV)

Lệnh gọi lại xác minh phía máy chủ là các yêu cầu URL, với tham số truy vấn do Google mở rộng, được Google gửi tới một hệ thống bên ngoài để thông báo rằng người dùng sẽ được thưởng vì đã tương tác với một quảng cáo có tặng thưởng hoặc quảng cáo xen kẽ có tặng thưởng. Lệnh gọi lại SSV (xác minh phía máy chủ) dành cho quảng cáo có tặng thưởng cung cấp thêm một lớp bảo vệ giúp ngăn chặn tình trạng giả mạo lệnh gọi lại phía máy khách để trao thưởng cho người dùng.

Hướng dẫn này giúp bạn hiểu cách xác minh lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng bằng cách sử dụng Tink (một thư viện mật mã bên thứ ba) để đảm bảo rằng tham số truy vấn trong lệnh gọi lại là giá trị hợp lệ. Mặc dù chúng tôi sử dụng Tink cho các mục đích của hướng dẫn này, nhưng bạn có thể sử dụng bất kỳ thư viện bên thứ ba nào có hỗ trợ ECDSA. Bạn cũng có thể kiểm tra máy chủ của mình bằng công cụ kiểm tra trong giao diện người dùng AdMob.

Hãy xem ví dụ đầy đủ chức năng này (thực hiện bằng ứng dụng spring-boot dựa trên Java).

Điều kiện tiên quyết

Sử dụng RewardedAdsVerifier của Tink

Kho lưu trữ Tink trên GitHub bao gồm một lớp trình trợ giúp RewardedAdsVerifier nhằm rút gọn đoạn mã cần thiết để xác minh lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng. Khi sử dụng lớp này cùng với thư viện mật mã bên thứ ba Tink, bạn có thể xác minh URL lệnh gọi lại bằng đoạn mã sau.

RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
    .fetchVerifyingPublicKeysWith(
        RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
    .build();
String rewardUrl = ...;
verifier.verify(rewardUrl);

Nếu không phát sinh trường hợp ngoại lệ trong quá trình thực thi phương thức verify(), thì URL lệnh gọi lại đã được xác minh thành công. Mục Trao thưởng cho người dùng trình bày một cách chi tiết các phương pháp hay nhất về thời điểm trao thưởng cho người dùng. Để biết thông tin chi tiết về các bước mà lớp này sẽ thực hiện để xác minh lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng, bạn có thể tìm hiểu trong mục Quy trình xác minh SSV dành cho quảng cáo có tặng thưởng theo cách thủ công.

Các tham số lệnh gọi lại SSV

Lệnh gọi lại xác minh phía máy chủ chứa các tham số truy vấn mô tả hoạt động tương tác với quảng cáo có tặng thưởng. Bạn có thể xem danh sách liệt kê tên tham số, nội dung mô tả và giá trị mẫu ở bên dưới. Hệ thống sẽ gửi các tham số theo thứ tự bảng chữ cái.

Tên thông số Mô tả Giá trị mẫu
ad_network Giá trị nhận dạng mạng quảng cáo cho mạng đã thực hiện quảng cáo này. Tên mạng tương ứng với giá trị mã được liệt kê trong phần Giá trị nhận dạng mạng quảng cáo. 1953547073528090325
ad_unit Mã đơn vị quảng cáo AdMob được dùng để yêu cầu quảng cáo có tặng thưởng. 2747237135
custom_data Chuỗi dữ liệu tùy chỉnh do setCustomData cung cấp.

Nếu ứng dụng không cung cấp chuỗi dữ liệu tùy chỉnh, thì giá trị thông số truy vấn này sẽ không hiển thị trong lệnh gọi lại SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id Khóa được sử dụng để xác minh lệnh gọi lại SSV. Giá trị này liên kết đến khóa công khai do máy chủ khóa AdMob cung cấp. 1234567890
reward_amount Số tiền thưởng như được chỉ định trong tùy chọn cài đặt đơn vị quảng cáo. 5
reward_item Vật phẩm thưởng như được chỉ định trong tùy chọn cài đặt đơn vị quảng cáo. xu
signature Chữ ký cho lệnh gọi lại SSV do AdMob tạo ra. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp Dấu thời gian về thời điểm người dùng được tặng thưởng dưới dạng Epoch time tính bằng mili giây. 1507770365237823
transaction_id Giá trị nhận dạng được mã hóa hex duy nhất cho từng sự kiện cấp phần thưởng do AdMob tạo ra. 18fa792de1bca816048293fc71035638
user_id Giá trị nhận dạng người dùng do setUserId cung cấp.

Nếu ứng dụng không cung cấp giá trị nhận dạng người dùng, thông số truy vấn này sẽ không hiển thị trong lệnh gọi lại SSV.

1234567

Giá trị nhận dạng mạng quảng cáo

Bảng bên dưới liệt kê tên mạng quảng cáo tương ứng đối với các giá trị nhận dạng mạng quảng cáo trong lệnh gọi lại SSV.

Tên mạng quảng cáo Id mạng quảng cáo
AdColony 15586990674969969776
AdMob 5450213213286189855
Applovin 1063618907739174004
Chartboost 2873236629771172317
Mạng đối tượng Facebook 10568273599589928883
Fuse 8914788932458531264
Fyber 4839637394546996422
InMobi 7681903010231960328
maio 7505118203095108657
myTarget 8450873672465271579
Nend 9383070032774777750
Tapjoy 7295217276740746030
Unity Ads 4970775877303683148
Vungle 1953547073528090325

Thưởng cho người dùng

Điều quan trọng là phải cân bằng trải nghiệm người dùng và xác thực phần thưởng khi quyết định thời điểm tặng thưởng cho người dùng. Các lệnh gọi lại phía máy chủ có thể bị trì hoãn trước khi tiếp cận các hệ thống bên ngoài. Do đó, phương pháp đề xuất hay nhất là sử dụng lệnh gọi lại phía máy khách để tặng thưởng cho người dùng ngay lập tức, trong khi thực hiện xác thực trên tất cả các phần thưởng khi nhận được lệnh gọi lại phía máy chủ. Phương pháp này mang lại trải nghiệm người dùng tốt trong khi vẫn đảm bảo tính hợp lệ của các phần thưởng được cấp.

Tuy nhiên, đối với các ứng dụng mà trong đó tính hợp lệ của phần thưởng là cực kỳ quan trọng (ví dụ: phần thưởng ảnh hưởng đến kinh tế trong trò chơi của ứng dụng) và sự chậm trễ trong việc cấp phần thưởng có thể chấp nhận được, thì việc đợi lệnh gọi lại phía máy chủ được xác minh có thể là phương pháp tốt nhất.

Dữ liệu tùy chỉnh

Các ứng dụng yêu cầu thêm dữ liệu bằng lệnh gọi lại xác minh phía máy chủ sẽ sử dụng tính năng dữ liệu tùy chỉnh của quảng cáo có tặng thưởng. Hệ thống sẽ chuyển tiếp mọi giá trị chuỗi mà bạn đã đặt trên đối tượng quảng cáo có tặng thưởng vào trong thông số truy vấn custom_datacủa lệnh gọi lại SSV. Nếu bạn không đặt giá trị dữ liệu tùy chỉnh, giá trị thông số truy vấn custom_data sẽ không hiển thị trong lệnh gọi lại SSV.

Đoạn mã dưới đây minh họa cách đặt dữ liệu tùy chỉnh trên đối tượng quảng cáo có tặng thưởng trước khi bạn yêu cầu một quảng cáo.

RewardedAd rewardedAd = new RewardedAd(this, "ca-app-pub-3940256099942544/5224354917");
ServerSideVerificationOptions options = new ServerSideVerificationOptions.Builder()
    .setCustomData("SAMPLE_CUSTOM_DATA_STRING")
    .build();
rewardedAd.setServerSideVerificationOptions(options);

Nếu muốn đặt chuỗi phần thưởng tùy chỉnh, bạn phải thực hiện trước khi hiển thị quảng cáo.

Xin lưu ý rằng chuỗi phần thưởng tùy chỉnh sẽ được thoát dạng phần trăm và có thể cần được giải mã khi lệnh gọi lại SSV phân tích cú pháp.

Xác minh SSV dành cho quảng cáo có tặng thưởng theo cách thủ công

Bạn có thể xem các bước mà lớp RewardedAdsVerifier sẽ thực hiện để xác minh SSV dành cho quảng cáo có tặng thưởng bên dưới. Mặc dù đoạn mã sử dụng nằm trong Java và sử dụng thư viện bên thứ ba Tink, nhưng bạn có thể triển khai các bước này bằng ngôn ngữ mình muốn, sử dụng bất kỳ thư viện bên thứ ba nào có hỗ trợ ECDSA.

Tìm nạp khóa công khai

Để xác minh lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng, bạn cần có khóa công khai do AdMob cung cấp.

Bạn có thể tìm nạp danh sách khóa công khai mà hệ thống dùng để xác thực lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng từ máy chủ khóa AdMob. Danh sách khóa công khai được cung cấp dưới dạng một JSON có định dạng tương tự như sau:

{
 "keys": [
    {
      keyId: 1916455855,
      pem: "-----BEGIN PUBLIC KEY-----\nMF...YTPcw==\n-----END PUBLIC KEY-----"
      base64: "MFkwEwYHKoZIzj0CAQYI...ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw=="
    },
    {
      keyId: 3901585526,
      pem: "-----BEGIN PUBLIC KEY-----\nMF...aDUsw==\n-----END PUBLIC KEY-----"
      base64: "MFYwEAYHKoZIzj0CAQYF...4akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw=="
    },
  ],
}

Để truy xuất khóa công khai, hãy kết nối với máy chủ khóa AdMob và tải các khóa đó xuống. Mã sau đây giúp bạn hoàn thành công việc này và lưu phép biểu diễn JSON của các khóa đó vào biến data.

String url = ...;
NetHttpTransport httpTransport = new NetHttpTransport.Builder().build();
HttpRequest httpRequest =
    httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(url));
HttpResponse httpResponse = httpRequest.execute();
if (httpResponse.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK) {
  throw new IOException("Unexpected status code = " + httpResponse.getStatusCode());
}
String data;
InputStream contentStream = httpResponse.getContent();
try {
  InputStreamReader reader = new InputStreamReader(contentStream, UTF_8);
  data = readerToString(reader);
} finally {
  contentStream.close();
}

Lưu ý: Chúng tôi sẽ thường xuyên xoay vòng các khóa công khai. Chúng tôi sẽ gửi email cho bạn để thông báo về việc xoay vòng sắp tới. Nếu đang lưu khóa công khai vào bộ nhớ đệm, bạn nên cập nhật các khóa đó khi nhận được email này.

Sau khi tìm nạp, các khóa công khai phải được phân tích cú pháp. Phương thức parsePublicKeysJson dưới đây sẽ lấy chuỗi JSON (như ví dụ ở trên) làm dữ liệu nhập và tạo mối liên kết từ giá trị key_id đến khóa công khai. Các khóa này sẽ được đóng gói dưới dạng đối tượng ECPublicKey trong thư viện Tink.

private static Map<Integer, ECPublicKey> parsePublicKeysJson(String publicKeysJson)
    throws GeneralSecurityException {
  Map<Integer, ECPublicKey> publicKeys = new HashMap<>();
  try {
    JSONArray keys = new JSONObject(publicKeysJson).getJSONArray("keys");
    for (int i = 0; i < keys.length(); i++) {
      JSONObject key = keys.getJSONObject(i);
      publicKeys.put(
          key.getInt("keyId"),
          EllipticCurves.getEcPublicKey(Base64.decode(key.getString("base64"))));
    }
  } catch (JSONException e) {
    throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
  }
  if (publicKeys.isEmpty()) {
    throw new GeneralSecurityException("No trusted keys are available.");
  }
  return publicKeys;
}

Lấy nội dung cần xác minh

2 tham số truy vấn cuối cùng của lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng luôn có thứ tự là signaturekey_id,. Các tham số truy vấn còn lại chỉ định nội dung cần xác minh. Giả sử bạn đã định cấu hình để AdMob gửi lệnh gọi lại phần thưởng đến https://www.myserver.com/mypath. Đoạn mã sau đây là một ví dụ về lệnh gọi lại SSV dành cho quảng cáo có tặng thưởng, trong đó in đậm nội dung cần xác minh.

https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins
&timestamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887

Mã dưới đây minh họa cách phân tích cú pháp nội dung cần được xác minh từ URL gọi lại dưới dạng mảng byte UTF-8.

public static final String SIGNATURE_PARAM_NAME = "signature=";
...
URI uri;
try {
  uri = new URI(rewardUrl);
} catch (URISyntaxException ex) {
  throw new GeneralSecurityException(ex);
}
String queryString = uri.getQuery();
int i = queryString.indexOf(SIGNATURE_PARAM_NAME);
if (i == -1) {
  throw new GeneralSecurityException("needs a signature query parameter");
}
byte[] queryParamContentData =
    queryString
        .substring(0, i - 1)
        // i - 1 instead of i because of & in the query string
        .getBytes(Charset.forName("UTF-8"));

Nhận chữ ký và key_id từ URL gọi lại

Sử dụng giá trị queryString từ bước trước, phân tích cú pháp thông số truy vấn signaturekey_id của URL gọi lại như bên dưới:

public static final String KEY_ID_PARAM_NAME = "key_id=";
...
String sigAndKeyId = queryString.substring(i);
i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME);
if (i == -1) {
  throw new GeneralSecurityException("needs a key_id query parameter");
}
String sig =
    sigAndKeyId.substring(
        SIGNATURE_PARAM_NAME.length(), i - 1 /* i - 1 instead of i because of & */);
int keyId = Integer.valueOf(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length()));

Thực hiện việc xác minh

Bước cuối cùng là xác minh nội dung của URL gọi lại bằng khóa công khai phù hợp. Hãy lấy đường liên kết mà phương thức parsePublicKeysJson trả về và sử dụng thông số key_id của URL gọi lại để nhận khóa công khai từ đường liên kết đó. Sau đó, hãy xác minh chữ ký bằng cách sử dụng khóa công khai đó. Bạn có thể xem các bước này bên dưới trong phương thức verify.

private void verify(final byte[] dataToVerify, int keyId, final byte[] signature)
    throws GeneralSecurityException {
  Map<Integer, ECPublicKey> publicKeys = parsePublicKeysJson();
  if (publicKeys.containsKey(keyId)) {
    foundKeyId = true;
    ECPublicKey publicKey = publicKeys.get(keyId);
    EcdsaVerifyJce verifier = new EcdsaVerifyJce(publicKey, HashType.SHA256, EcdsaEncoding.DER);
    verifier.verify(signature, dataToVerify);
  } else {
    throw new GeneralSecurityException("cannot find verifying key with key id: " + keyId);
  }
}

Nếu không phát sinh trường hợp ngoại lệ trong quá trình thực thi phương thức này, thì URL gọi lại đã được xác minh thành công.

Câu hỏi thường gặp

Tôi có thể lưu khóa công khai do máy chủ khóa AdMob cung cấp vào bộ nhớ đệm không?
Bạn nên lưu khóa công khai do máy chủ khóa AdMob cung cấp vào bộ nhớ đệm nhằm giảm số lượng thao tác mà bạn phải thực hiện để xác thực lệnh gọi lại SSV. Tuy nhiên, xin lưu ý rằng hệ thống sẽ thường xuyên xoay vòng các khóa công khai, do đó, bạn chỉ nên lưu các khóa công khai vào bộ nhớ đệm trong thời gian không quá 24 giờ.
Tần suất xoay vòng của các khóa công khai do máy chủ khóa AdMob cung cấp như thế nào?
Hệ thống sẽ xoay vòng các khóa công khai do máy chủ khóa AdMob cung cấp với lịch biểu thường xuyên thay đổi. Để đảm bảo rằng quy trình xác minh lệnh gọi lại SSV vẫn hoạt động bình thường, bạn chỉ nên lưu các khóa công khai vào bộ nhớ đệm trong thời gian không quá 24 giờ.
Điều gì sẽ xảy ra nếu tôi không thể truy cập vào máy chủ?
Google dự kiến sử dụng mã phản hồi trạng thái thành công HTTP 200 OK cho lệnh gọi lại SSV. Nếu bạn không thể truy cập vào máy chủ hoặc máy chủ không phản hồi như dự kiến, Google sẽ thử gửi lại các lệnh gọi lại SSV tối đa 5 lần trong khoảng thời gian một giây.
Làm cách nào để tôi có thể xác minh rằng các lệnh gọi lại SSV là của Google?
Bạn có thể sử dụng công cụ tra cứu DNS ngược để xác minh rằng các lệnh gọi lại SSV là của Google.