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 kèm theo 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 tặng thưởng khi tương tác với 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 SSO có tặng thưởng (xác minh phía máy chủ) cung cấp một lớp bảo vệ bổ sung chống lại việc giả mạo các lệnh gọi lại phía máy khách để tặng thưởng cho người dùng.

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

Hãy xem giải pháp này có hiệu quả hoàn toàn ví dụ bằng cách sử dụng spring-boot của Java.

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

Sử dụng AGDKsVerifier trong thư viện Ứng dụng Java của Tink

Kho lưu trữ Ứng dụng Java của Tink trên GitHub bao gồm RewardedAdsVerifier để rút gọn mã cần thiết nhằm xác minh lệnh gọi lại AFS có tặng thưởng. Khi sử dụng lớp này, bạn có thể xác minh URL gọi lại bằng mã sau.

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

Nếu phương thức verify() thực thi mà không phát sinh trường hợp ngoại lệ, thì lệnh gọi lại URL đã được xác minh thành công. Phần Tặng thưởng cho người dùng thông tin chi tiết về các phương pháp hay nhất về thời điểm người dùng sẽ được tặng thưởng. Đối với bảng 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 CSE có tặng thưởng, bạn có thể đọc qua bài viết Xác minh thủ công quảng cáo có tặng thưởng Xin nhắc lại.

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

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ả lượt tương tác với quảng cáo có tặng thưởng. Tên thông số, nội dung mô tả và giá trị mẫu là được liệt kê bên dưới. Hệ thống sẽ gửi các thông 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 nguồn quảng cáo của nguồn quảng cáo đã thực hiện quảng cáo này. Nguồn quảng cáo tên tương ứng với giá trị ID được liệt kê trong phần Quảng cáo giá trị nhận dạng nguồn. 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 tuỳ chỉnh do cung cấp bởi customRewardString .

Nếu ứng dụng không cung cấp chuỗi dữ liệu tuỳ chỉnh, thì tham số truy vấn này sẽ không xuất hiện trong lệnh gọi lại Vulkan.

SAMPLE_CUSTOM_DATA_STRING
key_id Khoá được dùng để xác minh lệnh gọi lại SSO. Giá trị này liên kết với một khoá công khai do máy chủ khoá AdMob cung cấp. 1234567890
reward_amount Số tiền thưởng như được chỉ định trong chế độ cài đặt đơn vị quảng cáo. 5
reward_item Vật phẩm thưởng như được chỉ định trong chế độ cài đặt đơn vị quảng cáo. xu
Chữ ký Chữ ký cho lệnh gọi lại CPF do AdMob tạo. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
dấu thời gian 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ã hoá 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 userIdentifier.

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

1234567

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

Tên và mã nguồn quảng cáo

广告来源名称 广告来源 ID
Aarki(出价)5240798063227064260
Ad Generation(出价)1477265452970951479
AdColony 15586990674969969776
AdColony(非 SDK)(出价)4600416542059544716
AdColony(出价)6895345910719072481
AdFalcon3528208921554210682
AdMob 广告联盟5450213213286189855
AdMob 广告联盟广告瀑布流1215381445328257950
ADResult10593873382626181482
AMoAd17253994435944008978
AppLovin1063618907739174004
AppLovin(出价)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform(出价)6432849193975106527
跨渠道 (MdotM)9372067028804390441
自定义事件18351550913290782395
DT Exchange*
* 在 2022 年 9 月 21 日之前,该广告联盟称为“Fyber Marketplace”。
2179455223494392917
EMX(出价)8497809869790333482
Fluct(出价)8419777862490735710
小风3376427960656545613
Fyber*
* 此广告来源用于生成历史报告。
4839637394546996422
i-mobile5208827440166355534
优化数字化(出价)159382223051638006
Index Exchange(出价)4100650709078789802
InMobi7681903010231960328
InMobi(出价)6325663098072678541
InMobi Exchange(出价)5264320421916134407
IronSource6925240245545091930
ironSource Ads(出价)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
LINE 广告联盟3025503711505004547
maio7505118203095108657
maio(出价)1343336733822567166
Media.net(出价)2127936450554446159
参与中介的自家广告6060308706800320801
Meta Audience Network*
* 在 2022 年 6 月 6 日之前,该广告联盟称为“Facebook Audience Network”。
10568273599589928883
Meta Audience Network(出价)*
* 在 2022 年 6 月 6 日之前,该广告联盟称为“Facebook Audience Network(出价)”。
11198165126854996598
Mintegral1357746574408896200
Mintegral(出价)6250601289653372374
MobFox8079529624516381459
MobFox(出价)3086513548163922365
MoPub(已弃用10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen(出价)*

* 在 2024 年 5 月 1 日之前,该广告联盟称为“UnrulyX”。

2831998725945605450
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange(出价)4873891452523427499
OpenX(出价)4918705482605678398
邦格尔4069896914521993236
Pangle(出价)3525379893916449117
PubMatic(出价)3841544486172445473
预订型广告系列7068401028668408324
RhythmOne(出价)2831998725945605450
Rubicon(出价)3993193775968767067
SK 星球734341340207269415
Sharethrough(出价)5247944089976324188
Smaato(出价)3362360112145450544
Equativ(出价)*

* 在 2023 年 1 月 12 日之前,该广告联盟称为“Smart Adserver”。

5970199210771591442
Sonobi(出价)3270984106996027150
Tapjoy7295217276740746030
Tapjoy(出价)4692500501762622178
Tencent GDT7007906637038700218
TripleLift(出价)8332676245392738510
Unity 广告4970775877303683148
Unity Ads(出价)7069338991535737586
Verizon Media7360851262951344112
Verve Group(出价)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

* 在 2023 年 1 月 30 日之前,该广告联盟称为“Vungle”。

1953547073528090325
Liftoff Monetize(出价)*

* 在 2023 年 1 月 30 日之前,该广告联盟称为“Vungle(出价)”。

4692500501762622185
Yieldmo(出价)4193081836471107579
YieldOne(出价)3154533971590234104
Zucks5506531810221735863

Tặng 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ị chậm trễ trước khi tiếp cận các hệ thống bên ngoài. Do đó, phương pháp hay nhất được đề xuấ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 vẫn thực hiện xác thực tất cả các phần thưởng khi nhận được lệnh gọi lại phía máy chủ. Chiến dịch này phương pháp tiếp cận này mang lại trải nghiệm tốt cho người dùng trong khi vẫn đảm bảo tính hợp lệ của phần thưởng.

Tuy nhiên, đối với các ứng dụng yêu cầu tính hợp lệ của phần thưởng là rất quan trọng (ví dụ: phần thưởng sẽ ảnh hưởng đến hoạt động kinh tế của ứng dụng trong trò chơi) đồng thời việc trao phần thưởng bị chậm trễ chấp nhận được, chờ lệnh gọi lại phía máy chủ được xác minh có thể là tốt nhất phương pháp tiếp cận.

Dữ liệu tùy chỉnh

Những ứng dụng cần có thêm dữ liệu trong lệnh gọi lại xác minh phía máy chủ nên sử dụng tính năng dữ liệu tuỳ chỉnh của quảng cáo có tặng thưởng. Bất kỳ giá trị chuỗi nào được đặt cho quảng cáo có tặng thưởng được truyền đến tham số truy vấn custom_data của lệnh gọi lại adb. Nếu không sau khi đã đặt giá trị dữ liệu tùy chỉnh, thì giá trị tham số truy vấn custom_data sẽ không là hiển thị trong lệnh gọi lại TCF.

Mã mẫu sau đây minh hoạ cách đặt các tuỳ chọn của tính năng SSO sau đã tải quảng cáo có tặng thưởng.

Swift

GADRewardedAd.load(withAdUnitID:"ca-app-pub-3940256099942544/1712485313",
                       request: request,
                       completionHandler: { [self] ad, error in
      if let error != error {
      rewardedAd = ad
      let options = GADServerSideVerificationOptions()
      options.customRewardString = "SAMPLE_CUSTOM_DATA_STRING"
      rewardedAd.serverSideVerificationOptions = options
    }

Objective-C

GADRequest *request = [GADRequest request];
[GADRewardedAd loadWithAdUnitID:@"ca-app-pub-3940256099942544/1712485313"
                        request:request
              completionHandler:^(GADRewardedAd *ad, NSError *error) {
                if (error) {
                  // Handle Error
                  return;
                }
                self.rewardedAd = ad;
                GADServerSideVerificationOptions *options =
                    [[GADServerSideVerificationOptions alloc] init];
                options.customRewardString = @"SAMPLE_CUSTOM_DATA_STRING";
                ad.serverSideVerificationOptions = options;
              }];

Quy trình xác minh AFS có tặng thưởng theo cách thủ công

Các bước mà lớp RewardedAdsVerifier sẽ thực hiện để xác minh một phần thưởng có tặng thưởng Dưới đây là sơ lược về tính năng Quy trình đăng ký xác thực (SSV) Mặc dù các đoạn mã được bao gồm nằm trong Java và tận dụng thư viện bên thứ ba của Tink. Bạn có thể triển khai các bước này trong ngôn ngữ bạn chọn, sử dụng bất kỳ thư viện bên thứ ba nào hỗ trợ ECDSA.

Tìm nạp khoá công khai

Để xác minh lệnh gọi lại SAML có tặng thưởng, bạn cần có khoá công khai do AdMob cung cấp.

Bạn có thể sử dụng danh sách các khoá công khai được dùng để xác thực lệnh gọi lại SAML có tặng thưởng được tìm nạp từ khoá AdMob máy chủ. Danh sách khoá công khai được cung cấp dưới dạng giá trị biểu diễn 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 khoá công khai, hãy kết nối với máy chủ khoá AdMob rồi tải khoá. Mã sau đây giúp hoàn thành công việc này và lưu JSON biểu diễn các khoá cho 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();
}

Xin lưu ý rằng chúng tôi sẽ thường xuyên xoay vòng các khoá công khai. Bạn sẽ nhận được email để thông báo cho bạn về vòng xoay sắp tới. Nếu đang lưu khoá công khai vào bộ nhớ đệm, bạn nên cập nhật các khoá khi nhận được email này.

Sau khi tìm nạp, bạn phải phân tích cú pháp các khoá công khai. Chiến lược phát hành đĩa đơn Phương thức parsePublicKeysJson bên dưới sẽ lấy một chuỗi JSON, chẳng hạn như ví dụ ở trên làm dữ liệu đầu vào và tạo một mối liên kết từ giá trị key_id đến khoá công khai, đượ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 được xác minh

Hai thông số truy vấn cuối cùng của lệnh gọi lại AFS có tặng thưởng luôn là signaturekey_id, theo thứ tự đó. Các tham số truy vấn còn lại chỉ định nội dung cần được 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ã dưới đây cho thấy một ví dụ về quảng cáo có tặng thưởng Lệnh gọi lại CPF, trong đó nội dung cần xác minh được làm nổi bật.

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 hoạ 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"));

Lấy 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 signature và Tham số truy vấn key_id từ URL gọi lại như minh hoạ dưới đây:

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

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 khoá công khai thích hợp. Lấy bản đồ được trả về từ Phương thức parsePublicKeysJson và sử dụng tham số key_id từ lệnh gọi lại URL để nhận khoá công khai qua mối liên kết đó. Sau đó, hãy xác minh chữ ký bằng khoá 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 phương thức này thực thi mà không phát sinh trường hợp ngoại lệ, thì URL gọi lại sẽ là đã xác minh thành công.

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

Tôi có thể lưu khoá công khai do máy chủ khoá AdMob cung cấp vào bộ nhớ đệm không?
Bạn nên lưu khóa công khai do khóa AdMob cung cấp vào bộ nhớ đệm máy chủ để giảm số lượng thao tác cần thực hiện nhằm xác thực tính năng SSO lệnh gọi lại. Tuy nhiên, xin lưu ý rằng chúng tôi thường xuyên xoay vòng các khoá công khai nên bạn không nên được lưu vào bộ nhớ đệm lâu hơn 24 giờ.
Tần suất xoay vòng các khoá công khai do máy chủ khoá AdMob cung cấp là bao nhiêu?
Các khoá công khai do máy chủ khoá AdMob cung cấp được xoay vòng trên một biến . Để đảm bảo rằng quy trình xác minh lệnh gọi lại SSO tiếp tục hoạt động thì khoá công khai không nên được lưu vào bộ nhớ đệm quá 24 giờ.
Điều gì 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 Quy trình tích hợp lệnh gọi lại. Nếu không thể truy cập vào máy chủ của bạn hoặc máy chủ không cung cấp Google sẽ cố gắng gửi lại lệnh gọi lại SSO tối đa 5 lần khoảng thời gian một giây.
Làm cách nào để xác minh rằng các lệnh gọi lại của Google đều đến từ 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 của hệ thống SSO là của Google.