คำขอ HTTP มีส่วนหัว เช่น User-Agent หรือ Content-Type นอกเหนือจากส่วนหัวที่เบราว์เซอร์แนบมาแล้ว แอป Android อาจเพิ่มส่วนหัวเพิ่มเติม เช่น คุกกี้หรือ Referrer ผ่านส่วนเสริม Intent EXTRA_HEADERS Chrome จะกรองส่วนหัวพิเศษบางส่วนเนื่องด้วยเหตุผลด้านความปลอดภัย
โดยขึ้นอยู่กับวิธีและตำแหน่งที่เปิดใช้ Intent
คำขอแบบข้ามต้นทางต้องมีเลเยอร์ความปลอดภัยเพิ่มเติมเนื่องจากไคลเอ็นต์และเซิร์ฟเวอร์ไม่ได้เป็นของบุคคลเดียวกัน คู่มือนี้จะอธิบายการเปิดคำขอดังกล่าวผ่าน Custom Tab ของ Chrome ซึ่งก็คือ Intent ที่เปิดจากแอปซึ่งเปิด URL ในแท็บเบราว์เซอร์ ก่อน Chrome 83 นักพัฒนาซอฟต์แวร์จะเพิ่มส่วนหัวใดก็ได้เมื่อเปิดแท็บที่กำหนดเอง ตั้งแต่เวอร์ชัน 83 เป็นต้นไป Chrome เริ่มกรองส่วนหัวแบบข้ามต้นทางทั้งหมด ยกเว้นส่วนหัวที่อยู่ในรายการที่อนุมัติ เนื่องจากส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติ ก่อให้เกิดความเสี่ยงด้านความปลอดภัย ตั้งแต่ Chrome 86 เป็นต้นไป คุณจะแนบส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติไปยังคำขอแบบข้ามต้นทางได้เมื่อเซิร์ฟเวอร์และไคลเอ็นต์มีความเกี่ยวข้องโดยใช้ลิงก์เนื้อหาดิจิทัล ลักษณะการทำงานนี้สรุปได้ในตารางต่อไปนี้
| เวอร์ชัน Chrome | ส่วนหัว CORS ที่อนุญาต |
|---|---|
| ก่อน Chrome 83 | approvelisted, non-approvelisted |
| Chrome 83 ถึง Chrome 85 | เพิ่มในรายการที่อนุมัติ |
| ตั้งแต่ Chrome 86 เป็นต้นไป | approvelisted, non-approvelisted เมื่อตั้งค่าลิงก์เนื้อหาดิจิทัล |
ตารางที่ 1: การกรองส่วนหัว CORS ที่ไม่อยู่ในรายการที่อนุมัติ
บทความนี้แสดงวิธีตั้งค่าการเชื่อมต่อที่ยืนยันแล้วระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์ และใช้การเชื่อมต่อดังกล่าว เพื่อส่งส่วนหัว HTTP ที่อยู่ในรายการที่อนุมัติและไม่อยู่ในรายการที่อนุมัติ คุณข้ามไปที่ส่วน การเพิ่มส่วนหัวพิเศษใน Intent ของแท็บที่กำหนดเองเพื่อดูโค้ดได้
ฉากหลัง
ส่วนหัวของคำขอ CORS ที่ได้รับอนุมัติเทียบกับส่วนหัวของคำขอ CORS ที่ไม่ได้รับอนุมัติ
การแชร์ทรัพยากรข้ามโดเมน (CORS) อนุญาตให้เว็บแอปพลิเคชันจากต้นทางหนึ่งขอ ทรัพยากรจากต้นทางอื่น รายการส่วนหัวที่ได้รับอนุมัติจาก CORS จะได้รับการดูแลในมาตรฐาน HTML ตัวอย่างส่วนหัวที่อนุมัติแล้วแสดงอยู่ในตารางถัดไป
| ส่วนหัว | คำอธิบาย |
|---|---|
| accept-language | โฆษณาภาษาธรรมชาติที่ไคลเอ็นต์เข้าใจ |
| content-language | อธิบายภาษาที่ตั้งใจใช้กับกลุ่มเป้าหมายปัจจุบัน |
| content-type | ระบุประเภทสื่อของทรัพยากร |
ตารางที่ 2: ตัวอย่างส่วนหัว CORS ที่อยู่ในรายการที่อนุมัติ
ระบบจะถือว่าส่วนหัวที่อยู่ในรายการที่อนุมัติแล้วปลอดภัย เนื่องจากไม่มีข้อมูลผู้ใช้ที่ละเอียดอ่อน และไม่น่าจะทำให้เซิร์ฟเวอร์ดำเนินการที่อาจเป็นอันตราย
ตัวอย่างส่วนหัวที่ไม่อยู่ในรายการที่อนุมัติแสดงอยู่ในตารางต่อไปนี้
| ส่วนหัว | คำอธิบาย |
|---|---|
| โทเค็นผู้ถือ | ตรวจสอบสิทธิ์ไคลเอ็นต์ที่เซิร์ฟเวอร์ |
| origin | ระบุแหล่งที่มาของคำขอ |
| คุกกี้ | มีคุกกี้ที่เซิร์ฟเวอร์ตั้งค่าไว้ |
ตารางที่ 3: ตัวอย่างส่วนหัว CORS ที่ไม่อยู่ในรายการที่อนุมัติ
มาตรฐาน HTML และเซิร์ฟเวอร์ไม่แนะนำให้แนบส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติไปยังคำขอ CORS เนื่องจากถือว่าคำขอแบบข้ามต้นทางมีเฉพาะส่วนหัวที่อยู่ในรายการที่อนุมัติ การส่งส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติ จากโดเมนแบบข้ามต้นทางจะทำให้แอปของบุคคลที่สามที่มีเจตนาร้ายสร้างส่วนหัวที่ใช้คุกกี้ของผู้ใช้ในทางที่ผิด ซึ่ง Chrome (หรือเบราว์เซอร์อื่น) จัดเก็บและแนบไปกับคำขอ คุกกี้จะ ตรวจสอบสิทธิ์ธุรกรรมของเซิร์ฟเวอร์ที่เป็นอันตรายซึ่งปกติแล้วจะทำไม่ได้
แนบส่วนหัวที่ได้รับอนุมัติจาก CORS ไปกับคำขอแท็บที่กำหนดเอง
แท็บที่กำหนดเองเป็นวิธีพิเศษในการเปิดหน้าเว็บในแท็บเบราว์เซอร์ที่กำหนดเอง คุณสร้างเจตนา Custom Tab ได้โดยใช้ CustomTabsIntent.Builder() นอกจากนี้ คุณยังแนบส่วนหัวกับ Intent เหล่านี้ได้โดยใช้ 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 จะกรองส่วนหัวที่ไม่อยู่ในรายการที่อนุมัติโดยค่าเริ่มต้น แม้ว่าเบราว์เซอร์อื่นๆ อาจมีลักษณะการทำงานที่แตกต่างกัน แต่ นักพัฒนาซอฟต์แวร์ควรคาดหวังว่าโดยทั่วไปแล้วระบบจะบล็อกส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติ
วิธีที่รองรับในการรวมส่วนหัวที่ไม่อยู่ในรายการที่อนุมัติไว้ในแท็บที่กำหนดเองคือการยืนยันการเชื่อมต่อแบบข้ามต้นทางโดยใช้ลิงก์การเข้าถึงแบบดิจิทัลก่อน ส่วนถัดไปจะแสดงวิธีตั้งค่า และเปิดใช้ Intent ของแท็บที่กำหนดเองพร้อมส่วนหัวที่จำเป็น
การเพิ่มส่วนหัวพิเศษไปยังความตั้งใจของแท็บที่กำหนดเอง
ตั้งค่าลิงก์เนื้อหาดิจิทัล
หากต้องการอนุญาตให้ส่งต่อส่วนหัวที่ไม่ได้อยู่ในรายการที่อนุมัติผ่าน Intent ของแท็บที่กำหนดเอง คุณต้องตั้งค่าลิงก์เนื้อหาดิจิทัลระหว่างแอปพลิเคชัน Android กับเว็บแอปพลิเคชัน ซึ่งจะยืนยันว่าผู้เขียนเป็นเจ้าของทั้ง 2 แอปพลิเคชัน
ทำตามคำแนะนำอย่างเป็นทางการเพื่อตั้งค่าลิงก์ชิ้นงานดิจิทัล สำหรับความสัมพันธ์ของลิงก์ ให้ใช้ "delegate_permission/common.use_as_origin"` ซึ่งระบุว่าทั้ง 2 แอปเป็นของต้นทางเดียวกันเมื่อยืนยันลิงก์แล้ว
สร้างความตั้งใจของแท็บที่กำหนดเองด้วยส่วนหัวเพิ่มเติม
การสร้างIntent ของแท็บที่กำหนดเองทำได้หลายวิธี คุณใช้เครื่องมือสร้างที่มีอยู่ใน androidX ได้โดยเพิ่มไลบรารีลงในทรัพยากร Dependency ของบิลด์
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) { }
};
ตั้งค่าการเรียกกลับที่เปิด Intent หลังจากตรวจสอบแล้ว
ระบบส่ง 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 จะกรองส่วนหัวเหล่านั้นโดยค่าเริ่มต้น การแนบไฟล์จะ อนุญาตเฉพาะไคลเอ็นต์และเซิร์ฟเวอร์ที่มีต้นทางเดียวกัน ซึ่งได้รับการยืนยันโดยลิงก์เนื้อหาดิจิทัล