
목차
🚨 문제 상황
🔍 문제 원인 분석
💡 해결방법: Manual OAuth Flow
🛠️ 설정 가이드
🔍 동작 원리 심화 이해
🎯 핵심 포인트 정리
📚 참고 자료
Google Colab에서 YouTube Data API를 사용해 비디오에 좋아요/싫어요를 하는 샘플 코드(https://github.com/youtube/api-samples/blob/master/python/like_video.py) 테스트를 시도하다가 다음과 같은 문제들에 직면했습니다.
localhost에서 연결을 거부했습니다.
WARNING:googleapiclient.http:Encountered 403 Forbidden with reason "insufficientPermissions"
❌ HTTP Error 403: Request had insufficient authentication scopes.
기존에 사용하던 일반적인 OAuth 인증 코드는 다음과 같았습니다:
from google_auth_oauthlib.flow import InstalledAppFlow
flow = InstalledAppFlow.from_client_secrets_file(client_secrets_file, SCOPES)
credentials = flow.run_local_server(port=0, open_browser=False)
from google.colab import auth
auth.authenticate_user()
Colab 내장 인증을 사용했지만 403 오류가 발생했습니다.
OAuth에는 여러 인증 방식이 있습니다.
| 방식 | Redirect URI | 사용 환경 | Colab 지원 |
|---|---|---|---|
| 웹 애플리케이션 | http://example.com/callback | 웹서버 | ❌ |
| 데스크톱 앱 | http://localhost:PORT | 로컬 환경 | ❌ |
| Manual/OOB | urn:ietf:wg:oauth:2.0:oob | 제한된 환경 | ✅ |
핵심은 urn:ietf:wg:oauth:2.0:oob (Out-of-Band) 방식을 사용하는 것입니다.
import os
import json
import requests
from urllib.parse import urlencode
from google.colab import files
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
# OAuth 설정
GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'
SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
def load_client_secrets():
"""OAuth 클라이언트 시크릿 파일 로드"""
print("📁 OAuth client secrets JSON 파일을 업로드해주세요:")
uploaded = files.upload()
if not uploaded:
raise Exception("파일이 업로드되지 않았습니다.")
client_secrets_file = list(uploaded.keys())[0]
with open(client_secrets_file, 'r') as f:
client_secrets = json.load(f)
# 클라이언트 정보 추출
if 'installed' in client_secrets:
client_info = client_secrets['installed']
elif 'web' in client_secrets:
client_info = client_secrets['web']
else:
raise Exception("올바르지 않은 클라이언트 시크릿 형식입니다.")
return {
'client_id': client_info['client_id'],
'client_secret': client_info['client_secret']
}
def get_oauth_credentials():
"""Manual OAuth 인증 수행"""
# 클라이언트 정보 로드
client_info = load_client_secrets()
client_id = client_info['client_id']
client_secret = client_info['client_secret']
# Step 1: 인증 URL 생성
auth_params = {
'client_id': client_id,
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob', # 🔑 핵심!
'scope': ' '.join(SCOPES),
'response_type': 'code',
'access_type': 'offline',
'prompt': 'consent'
}
auth_url = f"{GOOGLE_AUTH_URL}?{urlencode(auth_params)}"
# Step 2: 사용자 인증 안내
print("\n" + "="*60)
print("🌐 다음 URL을 새 브라우저 탭에서 열어주세요:")
print("="*60)
print(auth_url)
print("="*60)
print("\n📋 인증 절차:")
print("1. 위 URL을 클릭하거나 복사해서 브라우저에서 열기")
print("2. Google 계정으로 로그인")
print("3. 애플리케이션 권한 승인")
print("4. 나타나는 인증 코드를 복사")
print("5. 아래에 붙여넣기")
# Step 3: 인증 코드 입력받기
auth_code = input("\n🔑 인증 코드를 입력하세요: ").strip()
if not auth_code:
raise Exception("인증 코드가 입력되지 않았습니다.")
# Step 4: 액세스 토큰으로 교환
print("\n🔄 인증 코드를 액세스 토큰으로 교환 중...")
token_data = {
'client_id': client_id,
'client_secret': client_secret,
'code': auth_code,
'grant_type': 'authorization_code',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
}
response = requests.post(GOOGLE_TOKEN_URL, data=token_data)
if response.status_code != 200:
raise Exception(f"토큰 교환 실패: {response.text}")
token_info = response.json()
if 'error' in token_info:
raise Exception(f"OAuth 오류: {token_info['error_description']}")
# Step 5: Credentials 객체 생성
credentials = Credentials(
token=token_info['access_token'],
refresh_token=token_info.get('refresh_token'),
token_uri=GOOGLE_TOKEN_URL,
client_id=client_id,
client_secret=client_secret,
scopes=SCOPES
)
print("✅ 인증 완료!")
return build('youtube', 'v3', credentials=credentials)
def rate_video(youtube_service, video_id, rating):
"""YouTube 비디오 평가하기"""
try:
youtube_service.videos().rate(
id=video_id,
rating=rating
).execute()
print(f"✅ '{rating}' 평가가 비디오 {video_id}에 적용되었습니다!")
except Exception as e:
print(f"❌ 오류 발생: {e}")
raise
# 사용 예시
def main():
print("🎥 YouTube 비디오 평가하기")
try:
# 인증
youtube = get_oauth_credentials()
# 비디오 ID 입력
video_id = input("\n📹 YouTube 비디오 ID를 입력하세요: ").strip()
# 평가 선택
rating = input("평가를 선택하세요 (like/dislike/none): ").strip().lower()
if rating not in ['like', 'dislike', 'none']:
print("❌ 잘못된 평가입니다.")
return
# 평가 적용
rate_video(youtube, video_id, rating)
except Exception as e:
print(f"❌ 오류: {e}")
if __name__ == '__main__':
main()
1. 애플리케이션 → Google OAuth 서버
"인증 URL 생성 요청"
GET https://accounts.google.com/o/oauth2/v2/auth?
client_id=YOUR_CLIENT_ID&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
scope=https://www.googleapis.com/auth/youtube.force-ssl&
response_type=code&
access_type=offline
2. 사용자 → 브라우저
"인증 URL 방문 및 Google 로그인"
3. Google → 사용자
"권한 승인 화면 표시"
4. 사용자 → Google
"권한 승인"
5. Google → 사용자 (브라우저)
"인증 코드를 화면에 표시"
(일반적인 redirect 대신!)
6. 사용자 → 애플리케이션
"인증 코드 수동 입력"
7. 애플리케이션 → Google OAuth 서버
"인증 코드를 액세스 토큰으로 교환"
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
code=AUTHORIZATION_CODE&
grant_type=authorization_code&
redirect_uri=urn:ietf:wg:oauth:2.0:oob
8. Google → 애플리케이션
"액세스 토큰 및 리프레시 토큰 반환"
{
"access_token": "ya29.a0AfH6SMCX...",
"refresh_token": "1//04-rNHF5pI...",
"token_type": "Bearer",
"expires_in": 3599
}
9. 애플리케이션 → YouTube API
"액세스 토큰으로 API 호출"
기존 방식의 문제점:
# ❌ Colab에서 불가능한 작업들
import socket
socket.bind(('localhost', 8080)) # 포트 바인딩 제한
import webbrowser
webbrowser.open(url) # 브라우저 제어 불가
from http.server import HTTPServer
server = HTTPServer(...) # HTTP 서버 실행 불가
해결된 방식의 장점:
# ✅ Colab에서 가능한 작업들
import requests
requests.post(url, data=data) # HTTP 클라이언트 요청
user_input = input("입력: ") # 사용자 입력 받기
print("URL:", auth_url) # 텍스트 출력
| 환경 | 추천 방식 | 이유 |
|---|---|---|
| 로컬 PC | run_local_server() | 가장 편리함 |
| Colab | Manual OOB | 네트워크 제약 우회 |
| 서버 환경 | Service Account | 자동화에 적합 (YouTube API는 제외) |
혹시 다른 문제가 생기거나 궁금한 점이 있다면 언제든 댓글로 남겨주세요!