แอป Expo ใช้ HTTP ไม่ได้บน Android 9: เป็นเพราะ cleartext ไม่ใช่ VPN
บิลด์ Expo ที่เรียก API แบบ HTTP ธรรมดาล้มเหลวบน Android 9 และใหม่กว่า อุโมงค์ไม่ได้ผิด: แฟล็ก cleartext อยู่ในคอนฟิกของบิลด์
คุณปล่อยบิลด์ใหม่ ย้ายไปใช้ VPN แล้วหน้าจอเดิมก็โหลดไม่ขึ้น บน Android 9 และใหม่กว่า คำอธิบายที่พบบ่อยกว่ากลับน่าเบื่อกว่า: แพลตฟอร์มปฏิเสธ HTTP ธรรมดาสำหรับแอปที่กำหนดเป้าหมาย API 28 หรือสูงกว่า และ VPN ก็เปลี่ยนบริบทแค่พอให้สิ่งนี้ปรากฏ ข้อผิดพลาดที่คุณเห็นขึ้นอยู่กับไคลเอนต์ HTTP จึงไม่ค่อยบอกว่า cleartext
ความล้มเหลวนี้คืออะไรกันแน่
ตั้งแต่ Android 9 เป็นต้นมา แพลตฟอร์มบล็อก cleartext โดยค่าเริ่มต้นทันทีที่แอปกำหนดเป้าหมายระดับ API 28 หรือสูงกว่า คำขอไปยังที่อยู่ HTTP ธรรมดาถูกแพลตฟอร์มปฏิเสธก่อนที่แพ็กเก็ตเดียวจะออกจากอุปกรณ์ ข้อผิดพลาดที่คุณเห็นขึ้นอยู่กับไลบรารี HTTP: บางครั้งเป็นความล้มเหลวของเครือข่ายทั่วไป บางครั้งหมดเวลา บางครั้งเป็นข้อความที่ระบุ cleartext ตรง ๆ สิ่งที่มันไม่เคยทำคือไปถึงเซิร์ฟเวอร์ของคุณ Android เอกสารค่าเริ่มต้นและทางเลือกที่จำกัดตามโดเมนไว้ในคู่มือการกำหนดค่าความปลอดภัย
เบราว์เซอร์บนอุปกรณ์เดียวกันยังโหลดหน้าได้ และนั่นคือเบาะแสที่คนส่วนใหญ่มองข้าม เบราว์เซอร์ไม่อยู่ภายใต้นโยบายความปลอดภัยเครือข่ายของแอปคุณ
ทำไม VPN ถึงถูกตำหนิ
ปกติมีสามอย่างเปลี่ยนพร้อมกัน แอปอาจแปลงชื่อโฮสต์อื่นหลังเชื่อมต่อ VPN อุปกรณ์อาจเป็นเครื่องใหม่ที่มีบิลด์ใหม่กว่า หรือตัวบิลด์เองอาจย้ายไปยังระดับ API เป้าหมายที่กระตุ้นค่าเริ่มต้น VPN มองเห็นได้แต่ SDK เป้าหมายมองไม่เห็น VPN จึงรับตั๋วไป
เปรียบเทียบระดับเป้าหมายของบิลด์เก่าและใหม่ก่อนแตะการกำหนดค่าเครือข่าย แอปที่กำหนดเป้าหมาย 27 หรือต่ำกว่าได้รับการยกเว้น และนั่นอธิบายทุกรายงานที่ว่า «เครื่องเก่าของผมใช้ได้»
แฟล็กของ Expo ไปลงที่ไหนจริง ๆ
ในโปรเจกต์ Expo แบบจัดการ การเพิ่มการตั้งค่าใน app.json ไม่ได้แก้ไข Android manifest เพราะ prebuild และบิลด์สำหรับนักพัฒนาต่างหากที่สร้าง manifest ปลั๊กอินคอนฟิกคือสิ่งที่เขียนการตั้งค่าในขั้นนั้น วิธีที่รองรับคือ expo-build-properties ซึ่งกำหนดในการกำหนดค่าแอปด้วยตัวเลือก cleartext ใต้ android
{
"plugins": [
["expo-build-properties", {
"android": { "usesCleartextTraffic": true }
}]
]
}ตัวอย่างโค้ดตั้งค่าเพียงตัวเลือกของปลั๊กอิน ส่วน manifest ถูกสร้างภายหลัง รัน prebuild หรือบิลด์ไคลเอนต์นักพัฒนาใหม่ แล้วก่อนทดสอบซ้ำให้ตรวจ AndroidManifest.xml ที่สร้างขึ้นเพื่อหา android:usesCleartextTraffic เพราะการโหลดซ้ำด้วย JS อย่างเดียวจะคง manifest เก่าไว้ จับคู่เวอร์ชันปลั๊กอินกับ Expo SDK ที่โปรเจกต์รันอยู่ ตัวเลือกของปลั๊กอินเปลี่ยนไปมาระหว่างรีลีส และตัวอย่างที่คัดลอกมาจาก SDK อื่นเป็นแหล่งที่มาที่เชื่อถือได้ของการไม่ทำอะไรเงียบ ๆ
เวิร์กโฟลว์แบบ bare และตัวเลือกที่แคบกว่า
สำหรับ bare React Native สิ่งที่เทียบเท่าคือ android:usesCleartextTraffic บนอิลิเมนต์ application หรือการกำหนดค่าความปลอดภัยเครือข่ายที่อนุญาต cleartext เฉพาะโดเมนที่ระบุ ควรรูปแบบที่จำกัดตามโดเมน เพราะอนุญาต API ภายในและปล่อยอย่างอื่นไว้บน HTTPS ทั้งหมด มันยังบันทึกรายการยกเว้นของตัวเองด้วย และนั่นคือส่วนที่ผู้ตรวจจะถามถึง
ทั้งสองวิธีไม่เปลี่ยนค่าเริ่มต้นของแพลตฟอร์มสำหรับสิ่งที่คุณไม่ได้ระบุชื่อ
พิสูจน์จากล็อกของอุปกรณ์ก่อน
ก่อนแก้ไขการกำหนดค่า ให้ได้ความล้มเหลวที่แน่ชัด adb logcat บนอุปกรณ์ที่เชื่อมต่อจะแสดงการปฏิเสธ cleartext ของแพลตฟอร์มและระบุชื่อคอมโพเนนต์ที่ทำให้เกิด ซ็อกเก็ตที่ถูกปฏิเสธ ชื่อที่แปลงไม่ได้ หรือแฮนด์เชก TLS ที่ล้มเหลวชี้ไปที่อื่นโดยสิ้นเชิง และการเปิด cleartext จะทำให้แอพอ่อนแอลงโดยเปล่าประโยชน์
จากนั้นรักษาความซื่อสัตย์ของโปรดักชัน cleartext ยอมรับได้สำหรับปลายทางในแล็บที่ไม่สามารถให้ TLS และเฉพาะช่วงเวลาที่สั้นที่สุดที่คุณวางแผนได้ ถ้า API ยุติ TLS ภายในเครือข่ายส่วนตัวได้ ให้ใส่ใบรับรองในทรัสต์สโตร์ของอุปกรณ์และปล่อย cleartext ปิดไว้ เพราะอุโมงค์เข้ารหัสเส้นทางไปจนถึงทางออกของมัน และไม่มีอะไรเลยหลังจากนั้น
เมื่อปลายทางตอบผ่าน HTTPS แล้ว ให้ถอดสิทธิ์ออก สร้างใหม่ และทดสอบบน VPN และบน Wi-Fi ปกติในเซสชันเดียวกัน การแปลงชื่ออาจแตกต่างกันระหว่างสองเส้นทางนี้ และมีเพียงการเทียบทั้งคู่เท่านั้นที่บอกว่าฝั่งไหนพูดความจริง