הוספת כותרות נוספות לבקשת HTTP

בקשות HTTP מכילות כותרות כמו User-Agent או Content-Type. בנוסף לכותרות שמצורפות על ידי דפדפנים, אפליקציות ל-Android עשויות להוסיף כותרות נוספות, כמו Cookie או Referrer, באמצעות התוסף EXTRA_HEADERS Intent. מטעמי אבטחה, Chrome מסנן חלק מהכותרות הנוספות, בהתאם לאופן ולמקום שבהם מופעלת כוונה.

בקשות חוצות מקורות מחייבות שכבת אבטחה נוספת כי הלקוח והשרת לא נמצאים בבעלות של אותו צד. במדריך הזה נסביר איך לשלוח בקשות כאלה באמצעות כרטיסיות מותאמות ב-Chrome, כלומר באמצעות כוונות שמופעלות מאפליקציות שפותחות כתובת URL בכרטיסיית הדפדפן. עד גרסה Chrome 83, מפתחים יכלו להוסיף כותרות כשמפעילים כרטיסייה מותאמת אישית. החל מגרסה 83, Chrome התחיל לסנן את כל הכותרות של בקשות חוצות-מקורות חוץ מאלה שברשימת ההיתרים, כי כותרות שלא ברשימת ההיתרים הציבו סיכון אבטחה. החל מגרסה 86 של Chrome, אפשר לצרף כותרות שלא מופיעות ברשימת האישורים לבקשות חוצות מקורות, כשהשרת והלקוח קשורים באמצעות קישור לנכס דיגיטלי. ההתנהגות הזו מסוכמת בטבלה הבאה:

גרסת Chrome כותרות CORS מותרות
לפני Chrome 83 ברשימת ההיתרים, לא ברשימת ההיתרים
‫Chrome 83 עד Chrome 85 ברשימת ההיתרים
מגרסה Chrome 86 ואילך רשימת אישור, לא ברשימת האישור כשמגדירים קישור לנכס דיגיטלי

טבלה 1.: סינון של כותרות CORS שלא נכללות ברשימת ההיתרים.

במאמר הזה מוסבר איך להגדיר חיבור מאומת בין השרת ללקוח, ואיך להשתמש בו כדי לשלוח כותרות HTTP שנכללות ברשימת ההיתרים וכאלה שלא נכללות בה. אפשר לדלג אל הוספת כותרות נוספות ל-Intents של כרטיסיות בהתאמה אישית כדי לראות את הקוד.

רקע

כותרות של בקשות CORS ברשימת ההיתרים לעומת כותרות של בקשות CORS שלא ברשימת ההיתרים

שיתוף משאבים בין מקורות (CORS) מאפשר לאפליקציית אינטרנט ממקור אחד לבקש משאבים ממקור אחר. רשימת הכותרות CORS-approvelisted מתעדכנת בתקן HTML. בטבלה הבאה מוצגות דוגמאות לכותרות שנכללות ברשימת האישור:

כותרת תיאור
accept-language מפרסם שפות טבעיות שהלקוח מבין
שפת התוכן מתאר את השפה שמיועדת לקהל הנוכחי
content-type מציין את סוג המדיה של המשאב

טבלה 2: דוגמה לכותרות CORS ברשימת ההיתרים.

הכותרות שמופיעות ברשימת ההיתרים נחשבות בטוחות כי הן לא מכילות מידע רגיש על המשתמשים, וסביר להניח שהן לא יגרמו לשרת לבצע פעולות שעלולות לגרום נזק.

בטבלה הבאה מוצגות דוגמאות לכותרות שלא נכללות ברשימת הכותרות שאושרו:

כותרת תיאור
bearer-token מאמת את הלקוח בשרת
origin מציין את מקור הבקשה
קובץ Cookie הדוח מכיל קובצי Cookie שהוגדרו על ידי השרת

טבלה 3: דוגמה לכותרות CORS שלא מופיעות ברשימת הכותרות המאושרות.

תקן ה-HTML לא ממליץ לצרף כותרות שלא מופיעות ברשימת הכותרות המאושרות לבקשות CORS, והשרתים מניחים שבקשות ממקורות שונים מכילות רק כותרות שמופיעות ברשימת הכותרות המאושרות. שליחת כותרות שלא מופיעות ברשימת הכותרות המאושרות מדומיינים חוצי-מקורות תאפשר לאפליקציות זדוניות של צד שלישי ליצור כותרות שמשתמשות לרעה בקובצי Cookie של משתמשים ש-Chrome (או דפדפן אחר) מאחסן ומצרף לבקשות. קובצי ה-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 עם הכותרות הנדרשות.

הוספת כותרות נוספות ל-Intents של כרטיסיות בהתאמה אישית

כדי לאפשר העברה של כותרות שלא מופיעות ברשימת הכותרות המאושרות דרך כוונות של כרטיסיות בהתאמה אישית, צריך להגדיר קישור לנכס דיגיטלי בין אפליקציית Android לבין אפליקציית האינטרנט, כדי לוודא שהמפתח הוא הבעלים של שתי האפליקציות.

כדי להגדיר קישור לנכס דיגיטלי, פועלים לפי המדריך הרשמי. בקישור היחסים צריך להשתמש ב-"delegate_permission/common.use_as_origin"`, שמציין ששתי האפליקציות שייכות לאותו מקור אחרי שהקישור מאומת.

יצירת כוונת כרטיסייה בהתאמה אישית עם כותרות נוספות

יש כמה דרכים ליצור Custom Tabs intent. כדי להשתמש בכלי ליצירה שזמין ב-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;
}

חיבור של כרטיסיות בהתאמה אישית משמש להגדרת CustomTabsSession בין האפליקציה לבין כרטיסיית Chrome. אנחנו צריכים את הסשן כדי לאמת שהאפליקציה ואפליקציית האינטרנט שייכות לאותו מקור. האימות יצליח רק אם קישורי הנכסים הדיגיטליים הוגדרו בצורה נכונה.

מומלץ להתקשר אל CustomTabsClient.warmup(). היא מאפשרת לאפליקציית הדפדפן לבצע אתחול מראש ברקע ולזרז את תהליך הפתיחה של כתובת ה-URL.

// 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) { }
};

הגדרת קריאה חוזרת שמפעילה את הכוונה אחרי האימות

הפרמטר 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);

קוד אפליקציה להדגמה

פרטים נוספים על שירות הכרטיסיות בהתאמה אישית זמינים כאן. תוכלו לראות אפליקציה לדוגמה שעובדת במאגר GitHub‏ android-browser-helper.

סיכום

במדריך הזה הראינו איך להוסיף כותרות שרירותיות לבקשות CORS בכרטיסיות מותאמות אישית. אפשר לצרף כותרות שאושרו לרשימה לכל בקשת CORS בכרטיסיות מותאמות אישית. כותרות שלא מופיעות ברשימת הכותרות המאושרות נחשבות בדרך כלל לא בטוחות בבקשות CORS, ו-Chrome מסנן אותן כברירת מחדל. מותר לצרף אותם רק ללקוחות ולשרתים מאותו מקור, שאומתו באמצעות קישור לנכס דיגיטלי.