新增額外的 HTTP 要求標頭

HTTP 要求包含 User-Agent 或 Content-Type 等標頭。除了瀏覽器附加的標頭,Android 應用程式也可以透過 EXTRA_HEADERS Intent 額外資訊新增 Cookie 或參照網址等標頭。基於安全考量,Chrome 會根據啟動意圖的方式和位置,篩選部分額外標頭。

由於用戶端和伺服器不屬於同一方,跨來源要求需要額外一層安全防護。本指南將說明如何透過 Chrome 自訂分頁啟動這類要求,也就是從應用程式啟動意圖,在瀏覽器分頁中開啟網址。在 Chrome 83 之前,開發人員可以在啟動自訂分頁時新增任何標頭。從 83 版起,Chrome 開始篩除所有非核准清單的跨源標頭,因為非核准清單的標頭會造成安全風險。從 Chrome 86 開始,如果伺服器和用戶端使用數位資產連結建立關聯,即可將非核准清單中的標頭附加至跨來源要求。下表彙整了這項行為:

Chrome 版本 允許的 CORS 標頭
Chrome 83 之前的版本 已核准、未核准
Chrome 83 至 Chrome 85 已加入許可清單
Chrome 86 以上版本 設定數位資產連結時,會顯示「已核准」或「未核准」

表 1:篩除未列入核准清單的 CORS 標頭。

本文說明如何設定伺服器與用戶端之間的已驗證連線,並使用該連線傳送核准清單和非核准清單中的 HTTP 標頭。您可以跳至「在自訂分頁意圖中新增額外標頭」一節查看程式碼。

背景

已核准與未核准的 CORS 要求標頭

跨源資源共享 (CORS) 可讓某個來源的網頁應用程式要求不同來源的資源。CORS 核准的標頭清單會維護在 HTML 標準中。下表列出核准的標頭範例:

標題 說明
accept-language 宣傳客戶瞭解的自然語言
content-language 說明目前目標對象適用的語言
content-type 指出資源的媒體類型

表 2:已核准的 CORS 標頭範例。

核准清單中的標頭不含敏感的使用者資訊,也不太可能導致伺服器執行可能造成損害的作業,因此視為安全。

下表列出未列入核准清單的標頭範例:

標題 說明
bearer-token 在伺服器上驗證用戶端
origin 指出要求來源
餅乾 包含伺服器設定的 Cookie

表 3:未列入核准清單的 CORS 標頭範例。

HTML 標準不建議將非核准清單中的標頭附加至 CORS 要求,伺服器會假設跨源要求只包含核准清單中的標頭。從跨來源網域傳送未列入核准清單的標頭,會導致惡意第三方應用程式製作標頭,濫用 Chrome (或其他瀏覽器) 儲存並附加至要求的使用者 Cookie。Cookie 可能會驗證惡意伺服器交易,否則這些交易無法通過驗證。

將 CORS 允許清單中的標頭附加至自訂分頁要求

自訂分頁是一種特殊方式,可在自訂的瀏覽器分頁中啟動網頁。您可以使用 CustomTabsIntent.Builder() 建立自訂分頁意圖。您也可以使用 Bundle 和 Browser.EXTRA_HEADERS 旗標,將標頭附加至這些意圖:

CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

Bundle headers = new Bundle();
headers.putString("bearer-token", "Some token");
headers.putString("redirect-url", "Some redirect url");   
intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);

intent.launchUrl(Activity.this, Uri.parse("http://www.google.com"));

我們一律可將核准清單中的標頭附加至自訂分頁 CORS 要求。不過,Chrome 預設會篩除未列入核准清單的標頭。雖然其他瀏覽器可能會有不同行為,但開發人員應預期一般情況下,未列入核准清單的標頭會遭到封鎖。

如要在自訂分頁中加入未列入核准清單的標頭,請先使用數位存取連結驗證跨源連線。下一節將說明如何設定這些標頭,並使用必要標頭啟動 Custom Tabs 意圖。

在自訂分頁意圖中新增額外標頭

如要允許非核准清單中的標頭透過自訂分頁意圖傳遞,必須在 Android 和網頁應用程式之間設定數位資產連結,驗證作者同時擁有這兩個應用程式。

請按照官方指南設定數位資產連結。連結關係請使用「delegate_permission/common.use_as_origin」`,驗證連結後,這表示兩個應用程式屬於同一個來源。

使用額外標題建立自訂分頁意圖

建立自訂分頁意圖的方式有很多種,如要使用 androidX 中提供的建構工具,請將程式庫新增至建構依附元件:

implementation 'androidx.browser:browser:1.2.0'

建構意圖並新增額外標頭:

CustomTabsIntent constructExtraHeadersIntent(CustomTabsSession session) {
    CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

    // Example non-cors-approvelisted headers.
    Bundle headers = new Bundle();
    headers.putString("bearer-token", "Some token");
    headers.putString("redirect-url", "Some redirect url");
    intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
    return intent;
}

自訂分頁連線用於在應用程式和 Chrome 分頁之間設定 CustomTabsSession。我們需要工作階段來驗證應用程式和網頁應用程式是否屬於相同來源。 只有在數位資產連結設定正確無誤時,驗證才會通過。

建議撥打 CustomTabsClient.warmup()。這項功能可讓瀏覽器應用程式在背景預先初始化,加快網址開啟程序。

// Set up a connection that warms up and validates a session.
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(@NonNull ComponentName name, 
        @NonNull CustomTabsClient client) {
        // Create session after service connected.
        mSession = client.newSession(callback);
        client.warmup(0);
        // Validate the session as the same origin to allow cross origin headers.
        mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, 
            Uri.parse(url), null);
    }
    @Override
    public void onServiceDisconnected(ComponentName componentName) { }
};

設定在驗證後啟動 Intent 的 Callback

CustomTabsCallback 已傳入工作階段。我們設定了 onRelationshipValidationResult(),在來源驗證成功後啟動先前建立的 CustomTabsIntent。

// Set up a callback that launches the intent after session validated.
CustomTabsCallback callback = new CustomTabsCallback() {
    @Override
    public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, 
        boolean result, @Nullable Bundle extras) {
        // Launch custom tabs intent after session was validated as the same origin.
        CustomTabsIntent intent = constructExtraHeadersIntent(mSession);
        intent.launchUrl(MainActivity.this, Uri.parse(url));
    }
};

繫結自訂分頁服務連線

繫結服務會啟動服務,並最終呼叫連線的 onCustomTabsServiceConnected() 。別忘了適當取消繫結服務。繫結和取消繫結通常是在 onStart() 和 onStop() 活動生命週期方法中完成。

// Bind the custom tabs service connection.
// Call this in onStart()
CustomTabsClient.bindCustomTabsService(this,
    CustomTabsClient.getPackageName(MainActivity.this, null), connection);

// …
// Unbind the custom tabs service.
// Call this in onStop().
unbindService(connection);

試用版應用程式程式碼

如要進一步瞭解自訂分頁服務,請參閱這篇文章。如需可運作的範例應用程式,請參閱 android-browser-helper GitHub 存放區。

摘要

本指南說明如何將任意標頭新增至自訂分頁 CORS 要求。核准清單中的標頭可附加至每個自訂分頁 CORS 要求。一般而言,未列入核准清單的標頭在 CORS 要求中被視為不安全,Chrome 預設會篩除這些標頭。只有相同來源的用戶端和伺服器 (由數位資產連結驗證) 才能附加這些標頭。