ภาพรวม
เมื่อวันที่ 16 กุมภาพันธ์ 2022 เราได้ประกาศแผนที่จะทำให้การโต้ตอบ OAuth ของ Google ปลอดภัยยิ่งขึ้นโดยใช้ขั้นตอน OAuth ที่ปลอดภัยกว่า คู่มือนี้จะช่วยให้คุณเข้าใจการเปลี่ยนแปลงและขั้นตอนที่จำเป็นในการย้ายข้อมูลจากขั้นตอน OAuth Out-of-Band (OOB) ไปยังทางเลือกที่รองรับได้สำเร็จ
ความพยายามนี้เป็นมาตรการป้องกันการโจมตีแบบฟิชชิงและการแอบอ้างเป็นแอปในระหว่างการโต้ตอบกับปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google
OOB คืออะไร
Out-of-band (OOB) ของ OAuth หรือที่เรียกว่าตัวเลือกการคัดลอกและวางด้วยตนเอง เป็นขั้นตอนเดิมที่พัฒนาขึ้นเพื่อรองรับไคลเอ็นต์ที่ติดตั้งซึ่งไม่มี URI การเปลี่ยนเส้นทางเพื่อยอมรับข้อมูลเข้าสู่ระบบหลังจากที่ผู้ใช้อนุมัติคำขอยินยอมของ OAuth ขั้นตอน OOB มีความเสี่ยงต่อฟิชชิงระยะไกล และไคลเอ็นต์ต้องย้ายข้อมูลไปยังวิธีอื่นเพื่อป้องกันช่องโหว่นี้
เรากำลังเลิกใช้งานขั้นตอน OOB สำหรับแอปพลิเคชันทุกประเภท ซึ่งได้แก่ เว็บแอปพลิเคชัน, Android, iOS, Universal Windows Platform (UWP), แอป Chrome, ทีวีและอุปกรณ์อินพุตที่จำกัด, แอปบนเดสก์ท็อป
วันที่สำคัญในการปฏิบัติตามข้อกำหนด
- 28 กุมภาพันธ์ 2022 - บล็อกการใช้ OAuth ใหม่สำหรับขั้นตอน OOB
- 5 กันยายน 2022 - ข้อความเตือนที่แสดงต่อผู้ใช้อาจแสดงขึ้นสำหรับคำขอ OAuth ที่ไม่เป็นไปตามข้อกำหนด
- 3 ตุลาคม 2022 - เลิกใช้งานขั้นตอน OOB สำหรับไคลเอ็นต์ OAuth ที่สร้างก่อนวันที่ 28 กุมภาพันธ์ 2022
- 31 มกราคม 2023 - ไคลเอ็นต์ที่มีอยู่ทั้งหมดจะถูกบล็อก (รวมถึงไคลเอ็นต์ที่ได้รับการยกเว้น)
ข้อความแสดงข้อผิดพลาดที่แสดงต่อผู้ใช้จะปรากฏขึ้นสำหรับคำขอที่ไม่เป็นไปตามข้อกำหนด ข้อความจะแจ้งให้ผู้ใช้ทราบว่าแอปถูกบล็อกขณะแสดงอีเมลสนับสนุนที่คุณลงทะเบียนไว้ในหน้าจอขอความยินยอม OAuth ในคอนโซล Google Cloud
การย้ายข้อมูลให้เสร็จสมบูรณ์มี 2 ขั้นตอนหลักๆ ดังนี้
- ตรวจสอบว่าคุณได้รับผลกระทบหรือไม่
- เปลี่ยนไปใช้ทางเลือกที่ปลอดภัยกว่าหากคุณได้รับผลกระทบ
ตรวจสอบว่าคุณได้รับผลกระทบหรือไม่
การเลิกใช้งานนี้จะมีผลกับแอปเวอร์ชันที่ใช้งานจริงเท่านั้น (เช่น แอปที่มีสถานะการเผยแพร่เป็นเวอร์ชันที่ใช้งานจริง) ขั้นตอนจะยังคงใช้ได้กับแอปที่มีสถานะการเผยแพร่เป็นการทดสอบ
ตรวจสอบสถานะการเผยแพร่ในหน้าการสร้างแบรนด์ OAuth ของคอนโซล Google Cloud และดำเนินการต่อในขั้นตอนถัดไปหากคุณใช้ขั้นตอน OOB ในโปรเจ็กต์ที่มีสถานะการเผยแพร่เป็น "อยู่ระหว่างการผลิต"
วิธีตรวจสอบว่าแอปของคุณใช้โฟลว์ OOB หรือไม่
ตรวจสอบโค้ดของแอปหรือการเรียกเครือข่ายขาออก (ในกรณีที่แอปใช้ไลบรารี OAuth) เพื่อดูว่าคำขอการให้สิทธิ์ OAuth ของ Google ที่แอปสร้างขึ้นใช้ค่า URI การเปลี่ยนเส้นทาง OOB หรือไม่
ตรวจสอบโค้ดของแอปพลิเคชัน
ตรวจสอบส่วนของโค้ดแอปพลิเคชันที่คุณเรียกใช้ปลายทางการให้สิทธิ์ของ Google OAuth และพิจารณาว่าพารามิเตอร์ redirect_uri มีค่าใดค่าหนึ่งต่อไปนี้หรือไม่
redirect_uri=urn:ietf:wg:oauth:2.0:oobredirect_uri=urn:ietf:wg:oauth:2.0:oob:autoredirect_uri=oob
ตัวอย่างคำขอขั้นตอนการเปลี่ยนเส้นทาง OOB มีลักษณะดังนี้
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& scope=<SCOPES>& state=<STATE>& redirect_uri=urn:ietf:wg:oauth:2.0:oob& client_id=<CLIENT_ID>
ตรวจสอบการเรียกใช้เครือข่ายขาออก
วิธีการตรวจสอบการเรียกใช้เครือข่ายจะแตกต่างกันไปตามประเภทแอปพลิเคชัน
- เว็บแอปพลิเคชัน - ตรวจสอบกิจกรรมเครือข่ายใน Chrome
- Android - ตรวจสอบการจราจรของข้อมูลในเครือข่ายด้วยเครื่องมือตรวจสอบเครือข่าย
- แอป Chrome
- ไปที่หน้าส่วนขยาย Chrome
- เลือกช่องทำเครื่องหมายโหมดนักพัฒนาซอฟต์แวร์ที่มุมขวาบนของหน้าส่วนขยาย
- เลือกส่วนขยายที่ต้องการตรวจสอบ
- คลิกลิงก์หน้าพื้นหลังในส่วนตรวจสอบมุมมองของหน้าส่วนขยาย
- กล่องโต้ตอบเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะเปิดขึ้นเพื่อให้คุณตรวจสอบการจราจรของข้อมูลในเครือข่ายได้ในแท็บเครือข่าย
- iOS - การวิเคราะห์การเข้าชม HTTP ด้วย Instruments
- แอปบนเดสก์ท็อป - ใช้เครื่องมือจับภาพเครือข่ายที่พร้อมใช้งานสำหรับระบบปฏิบัติการที่พัฒนาแอปขึ้นมา
ขณะตรวจสอบการเรียกใช้เครือข่าย ให้มองหาคำขอที่ส่งไปยังปลายทางการให้สิทธิ์ OAuth ของ Google และพิจารณาว่าพารามิเตอร์ redirect_uri มีค่าใดค่าหนึ่งต่อไปนี้หรือไม่
redirect_uri=urn:ietf:wg:oauth:2.0:oobredirect_uri=urn:ietf:wg:oauth:2.0:oob:autoredirect_uri=oob
ตัวอย่างคำขอขั้นตอนการเปลี่ยนเส้นทาง OOB มีลักษณะดังนี้
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& scope=<SCOPES>& state=<STATE>& redirect_uri=urn:ietf:wg:oauth:2.0:oob& client_id=<CLIENT_ID>
ย้ายข้อมูลไปยังทางเลือกที่ปลอดภัย
ไคลเอ็นต์มือถือ (Android และ iOS)
หากพบว่าแอปใช้โฟลว์ OOB กับประเภทแอปพลิเคชัน OAuth ของ Android หรือ iOS คุณควรย้ายข้อมูลไปใช้ SDK ที่แนะนำ (Android, iOS)
SDK ช่วยให้คุณเข้าถึง Google APIs และจัดการการเรียกใช้ปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google ทั้งหมด
ลิงก์เอกสารประกอบต่อไปนี้ให้ข้อมูลเกี่ยวกับวิธีใช้ SDK ที่แนะนำเพื่อเข้าถึง Google APIs โดยไม่ต้องใช้ URI การเปลี่ยนเส้นทาง OOB
เข้าถึง Google APIs ใน Android
การเข้าถึงฝั่งไ���ลเอ็นต์
ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ฝั่งไคลเอ็นต์ใน Android โดยใช้ไลบรารี Android ของ Google Identity Services ที่แนะนำ
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA);
AuthorizationRequest authorizationRequest =
AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener(
authorizationResult -> {
if (authorizationResult.hasResolution()) {
// Access needs to be granted by the user
PendingIntent pendingIntent =
authorizationResult.getPendingIntent();
try {
startIntentSenderForResult(
pendingIntent.getIntentSender(),
REQUEST_AUTHORIZE, null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
"Couldn't start Authorization UI: "
+ e.getLocalizedMessage());
}
} else {
// Access already granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
ส่ง authorizationResult ไปยังเมธอดที่กำหนดไว้เพื่อบันทึกเนื้อหาลงในโฟลเดอร์ไดรฟ์ของผู้ใช้ authorizationResult มีเมธอด getAccessToken() ที่แสดงผลโทเค็นเพื่อการเข้าถึง
การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ฝั่งเซิร์ฟเวอร์ใน Android
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA); AuthorizationRequest authorizationRequest = AuthorizationRequest.builder() .requestOfflineAccess(webClientId) .setRequestedScopes(requestedScopes) .build(); Identity.getAuthorizationClient(activity) .authorize(authorizationRequest) .addOnSuccessListener( authorizationResult -> { if (authorizationResult.hasResolution()) { // Access needs to be granted by the user PendingIntent pendingIntent = authorizationResult.getPendingIntent(); try { startIntentSenderForResult( pendingIntent.getIntentSender(), REQUEST_AUTHORIZE, null, 0, 0, 0, null); } catch (IntentSender.SendIntentException e) { Log.e( TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage()); } } else { String authCode = authorizationResult.getServerAuthCode(); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
authorizationResult มีเมธอด getServerAuthCode() ที่แสดงผลรหัสการให้สิทธิ์ซึ่งคุณสามารถส่งไปยังแบ็กเอนด์เพื่อรับโทเค็นเพื่อการเข้าถึงและโทเค็นการรีเฟรช
เข้าถึง Google APIs ในแอป iOS
การเข้าถึงฝั่งไคลเอ็นต์
ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ฝั่งไคลเอ็นต์ใน iOS
user.authentication.do { authentication, error in guard error == nil else { return } guard let authentication = authentication else { return } // Get the access token to attach it to a REST or gRPC request. let accessToken = authentication.accessToken // Or, get an object that conforms to GTMFetcherAuthorizationProtocol for // use with GTMAppAuth and the Google APIs client library. let authorizer = authentication.fetcherAuthorizer() }
ใช้โทเค็นเพื่อการเข้าถึงเพื่อเรียก API โดยใส่โทเค็นเพื่อการเข้าถึงไว้ในส่วนหัวของคำขอ REST หรือ gRPC (Authorization: Bearer <var>ACCESS_TOKEN</var>) หรือใช้ผู้อนุมัติการดึงข้อมูล (GTMFetcherAuthorizationProtocol) กับไลบรารีของไคลเอ็นต์ Google APIs สำหรับ Objective-C สำหรับ REST
ดูคู่มือการเข้าถึงฝั่งไคลเอ็นต์เกี่ยวกับวิธีเข้าถึง Google API ในฝั่งไคลเอ็นต์
การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ฝั่งเซิร์ฟเวอร์เพื่อรองรับไคลเอ็นต์ iOS
GIDSignIn.sharedInstance.signIn(
with: signInConfig, presenting: self
) { user, error in
guard error == nil else { return }
guard let user = user else { return }
// request a one-time authorization code that your server exchanges for
// an access token and refresh token
let authCode = user.serverAuthCode
}ดูคู่มือการเข้าถึงฝั่งเซิร์ฟเวอร์เกี่ยวกับวิธีเข้าถึง Google API จากฝั่งเซิร์ฟเวอร์
ไคลเอ็นต์แอป Chrome
หากพบว่าแอปใช้ขั้นตอน OOB ในไคลเอ็นต์แอป Chrome คุณควรเปลี่ยนไปใช้ Chrome Identity API
ตัวอย่างต่อไปนี้แสดงวิธีรับรายชื่อติดต่อของผู้ใช้ทั้งหมดโดยไม่ต้องใช้ URI การเปลี่ยนเส้นทาง OOB
window.onload = function() { document.querySelector('button').addEventListener('click', function() { // retrieve access token chrome.identity.getAuthToken({interactive: true}, function(token) { // .......... // the following example shows how to use a retrieved access token with an // appropriate scope to call the Google People API contactGroups.get endpoint fetch( 'https://people.googleapis.com/v1/contactGroups/all' + '?maxMembers=20&key=API_KEY', init) .then((response) => response.json()) .then(function(data) { console.log(data) }); }); }); };
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีตรวจสอบสิทธิ์ผู้ใช้และเรียกใช้ปลายทางของ Google ด้วย Chrome Identity API ได้ในคู่มือ Chrome Identity API
เว็บแอปพลิเคชัน
หากพิจารณาแล้วว่าแอปของคุณใช้โฟลว์ OOB สำหรับเว็บแอปพลิเคชัน คุณควรย้ายข้อมูลไปใช้ไลบรารีของไคลเอ็นต์ Google API รายการใดรายการหนึ่งของเรา ดูรายการไลบรารีของไคลเอ็นต์ Google API สำหรับภาษาโปรแกรมต่างๆ
ไลบรารีช่วยให้เข้าถึง Google APIs ได้ง่ายขึ้นและจัดการการเรียกทั้งหมดไปยังปลายทางของ Google
การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
โหมดการเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์) กำหนดให้คุณต้องทำดังนี้
- ตั้งค่าเซิร์ฟเวอร์และกำหนดปลายทางที่เข้าถึงได้แบบสาธารณะ (URI การเปลี่ยนเส้นทาง) เพื่อรับรหัสการให้สิทธิ์
- กำหนดค่า URI การเปลี่ยนเส้นทางในหน้าไคลเอ็นต์ของคอนโซล Google Cloud
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่าง NodeJS ของการใช้ Google Drive API เพื่อแสดงรายการไฟล์ Google ไดรฟ์ของผู้ใช้ในฝั่งเซิร์ฟเวอร์โดยไม่ต้องใช้ URI การเปลี่ยนเส้นทาง OOB
async function main() { const server = http.createServer(async function (req, res) { if (req.url.startsWith('/oauth2callback')) { let q = url.parse(req.url, true).query; if (q.error) { console.log('Error:' + q.error); } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { // TODO(developer): Handle response / error. }); } } }
ดูคู่มือเว็บแอปฝั่งเซิร์ฟเวอร์เกี่ยวกับวิธีเข้าถึง Google API จากฝั่งเซิร์ฟเวอร์
การเข้าถึงฝั่งไคลเอ็นต์
ข้อมูลโค้ดต่อไปนี้ใน JavaScript แสดงตัวอย่างการใช้ Google API เพื่อเข้าถึงกิจกรรมในปฏิทินของผู้ใช้ในฝั่งไคลเอ็นต์
// initTokenClient() initializes a new token client with your // web app's client ID and the scope you need access to const client = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_GOOGLE_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly', // callback function to handle the token response callback: (tokenResponse) => { if (tokenResponse && tokenResponse.access_token) { gapi.client.setApiKey('YOUR_API_KEY'); gapi.client.load('calendar', 'v3', listUpcomingEvents); } }, }); function listUpcomingEvents() { gapi.client.calendar.events.list(...); }
ดูคู่มือเว็บแอปฝั่งไคลเอ็นต์เกี่ยวกับวิธีเข้าถึง Google API จากฝั่งไคลเอ็นต์
ไคลเอ็นต์บนเดสก์ท็อป
หากพบว่าแอปใช้ขั้นตอน OOB ในไคลเอ็นต์เดสก์ท็อป คุณควรย้ายข้อมูลไปใช้ขั้นตอนที่อยู่ IP ของ Loopback (localhost หรือ 127.0.0.1)