개요
2022년 2월 16일, Google은 더 안전한 OAuth 흐름을 사용하여 Google OAuth 상호작용을 더 안전하게 만들 계획을 발표했습니다. 이 가이드는 OAuth 대역 외 (OOB) 흐름에서 지원되는 대안으로 성공적으로 이전하는 데 필요한 변경사항과 단계를 이해하는 데 도움이 됩니다.
이러한 노력은 Google의 OAuth 2.0 승인 엔드포인트와 상호작용하는 동안 피싱 및 앱 가장 공격을 방지하기 위한 보호 조치입니다.
OOB란 무엇인가요?
수동 복사 및 붙여넣기 옵션이라고도 하는 OAuth 대역 외 (OOB)는 사용자가 OAuth 동의 요청을 승인한 후 사용자 인증 정보를 수락할 리디렉션 URI가 없는 설치된 클라이언트를 지원하기 위해 개발된 기존 ���름입니다. OOB 흐름은 원격 피싱 위험을 초래하므로 클라이언트는 이 취약점으로부터 보호하기 위해 대체 방법으로 이전해야 합니다.
OOB 흐름은 모든 애플리케이션 유형(웹 애플리케이션, Android, iOS, 범용 Windows 플랫폼(UWP), Chrome 앱, TV 및 입력 제한 기기, 데스크톱 앱)에서 지원 중단됩니다.
주요 규정 준수 날짜
- 2022년 2월 28일 - OOB 흐름에 대한 새로운 OAuth 사용이 차단됨
- 2022년 9월 5일 - 규정을 준수하지 않는 OAuth 요청에 사용자 대상 경고 메시지가 표시될 수 있습니다.
- 2022년 10월 3일 - 2022년 2월 28일 이전에 생성된 OAuth 클라이언트에 대해 OOB 흐름이 지원 중단됩니다.
- 2023년 1월 31일 - 모든 기존 클라이언트가 차단됨 (예외 클라이언트 포함)
규정을 준수하지 않는 요청의 경우 사용자 대상 오류 메시지가 표시됩니다. 이 메시지는 앱이 차단되었음을 사용자에게 알리며 Google Cloud 콘솔의 OAuth 동의 화면에 등록한 지원 이메일을 표시합니다.
이전 프로세스를 완료하는 데는 두 가지 주요 단계가 있습니다.
- 영향을 받는지 확인합니다.
- 영향을 받는 경우 더 안전한 대안으로 이전하세요.
영향을 받는지 여부 확인
이 지원 중단은 프로덕션 앱 (즉, 게시 상태가 프로덕션으로 설정된 앱)에만 적용됩니다. 테스트 게시 상태인 앱의 경우 흐름이 계속 작동합니다.
Google Cloud 콘솔의 OAuth 브랜딩 페이지에서 게시 상태를 검토하고 '프로덕션' 게시 상태의 프로젝트에서 OOB 흐름을 사용하는 경우 다음 단계로 진행합니다.
앱에서 OOB 흐름을 사용 중인지 확인하는 방법
앱 코드 또는 발신 네트워크 호출 (앱에서 OAuth 라이브러리를 사용하는 경우)을 검사하여 앱에서 실행하는 Google OAuth 승인 요청에서 OOB 리디렉션 URI 값을 사용하는지 확인합니다.
애플리케이션 코드 검사
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 - Instruments를 사용하여 HTTP 트래픽 분석
- 데스크톱 앱 - 앱이 개발된 운영체제에 사용할 수 있는 네트워크 캡처 도구를 사용합니다.
네트워크 호출을 검사하는 동안 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>
안전한 대안으로 이전하기
모바일 클라이언트 (Android 및 iOS)
앱이 Android 또는 iOS OAuth 애플리케이션 유형과 함께 OOB 흐름을 사용하고 있다고 판단되면 권장 SDK(Android, iOS)를 사용하도록 이전해야 합니다.
SDK는 Google API에 액세스하는 데 도움이 되며 Google의 OAuth 2.0 승인 엔드포인트에 대한 모든 호출을 처리합니다.
다음 문서 링크에서는 OOB 리디렉션 URI를 사용하지 않고 권장 SDK를 사용하여 Google API에 액세스하는 방법을 설명합니다.
Android에서 Google API 액세스
클라이언트 측 액세스
다음 예에서는 권장되는 Google ID 서비스 Android 라이브러리를 사용하여 Android의 클라이언트 측에서 Google API에 액세스하는 방법을 보여줍니다.
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() 메서드가 있습니다.
서버 측 (오프라인) 액세스
다음 예시에서는 Android의 서버 측에서 Google API에 액세스하는 방법을 보여줍니다.
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() 메서드가 있습니다.
iOS 앱에서 Google API에 액세스
클라이언트 측 액세스
다음 예에서는 iOS의 클라이언트 측에서 Google API에 액세스하는 방법을 보여줍니다.
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() }
REST 또는 gRPC 요청의 헤더에 액세스 토큰을 포함하거나(Authorization: Bearer <var>ACCESS_TOKEN</var>) REST용 Objective-C용 Google API 클라이언트 라이브러리와 함께 가져오기 승인 도구 (GTMFetcherAuthorizationProtocol)를 사용하여 액세스 토큰을 사용해 API를 호출합니다.
클라이언트 측에서 Google API에 액세스하는 방법에 대한 클라이언트 측 액세스 가이드를 검토합니다.
서버 측 (오프라인) 액세스
다음 예시에서는 iOS 클라이언트를 지원하기 위해 서버 측에서 Google API에 액세스하는 방법을 보여줍니다.
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 앱 클라이언트
앱이 Chrome 앱 클라이언트에서 OOB 흐름을 사용하고 있다고 판단되면 Chrome Identity API를 사용하도록 이전해야 합니다.
다음 예시에서는 OOB 리디렉션 URI를 사용하지 않고 모든 사용자 연락처를 가져오는 방법을 보여줍니다.
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) }); }); }); };
Chrome Identity API를 사용하여 사용자를 인증하고 Google 엔드포인트를 호출하는 방법에 관한 자세한 내용은 Chrome Identity API 가이드를 참고하세요.
웹 애플리케이션
앱이 웹 애플리케이션에 OOB 흐름을 사용하고 있다고 판단되면 Google API 클라이언트 라이브러리 중 하나를 사용하도록 이전해야 합니다. 다양한 프로그래밍 언어에 대한 Google API 클라이언트 라이브러리 목록을 참조하세요.
라이브러리는 Google API에 대한 액세스를 간소화하고 Google 엔드포인트에 대한 모든 호출을 처리합니다.
서버 측 (오프라인) 액세스
서버 측 (오프라인) 액세스 모드를 사용하려면 다음을 수행해야 합니다.
- 서버를 설정하고 승인 코드를 수신할 공개적으로 액세스 가능한 엔드포인트 (리디렉션 URI)를 정의합니다.
- Google Cloud 콘솔의 클라이언트 페이지에서 리디렉션 URI를 구성합니다.
다음 코드 스니펫은 Google Drive API를 사용하여 OOB 리디렉션 URI를 사용하지 않고 서버 측에서 사용자의 Google Drive 파일을 나열하는 NodeJS 예시를 보여줍니다.
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 주소 (localhost 또는 127.0.0.1) 흐름을 사용하도록 이전해야 합니다.