feat(sheets): 사용자 OAuth 리프레시 토큰 인증 지원

조직 정책(iam.disableServiceAccountKeyCreation)으로 서비스 계정 키를 만들 수
없는 환경 대응. 서비스 계정이 설정돼 있으면 그쪽을 먼저 쓰고, 없으면
GOOGLE_SHEETS_OAUTH_REFRESH_TOKEN + client id/secret 으로 인증한다.
(client id/secret 미지정 시 로그인용 GOOGLE_CLIENT_ID/SECRET 재사용)

scripts/google_sheets_authorize.py: 데스크톱 앱 OAuth 클라이언트로
1회 동의해 refresh token 을 발급받는 스크립트.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 14:45:21 +09:00
parent db19a2fcbf
commit 63cfdc87ec
5 changed files with 187 additions and 22 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Google 스프레드시트 쓰기용 refresh token 1회 발급 스크립트.
조직 정책(`iam.disableServiceAccountKeyCreation`)으로 서비스 계정 키를 만들 수 없을 때
사용자 계정 OAuth 로 서버가 시트에 기록하도록 하기 위한 토큰을 받는다.
사전 준비 (Google Cloud Console)
1. API 및 서비스 → 라이브러리 → "Google Sheets API" 사용
2. API 및 서비스 → OAuth 동의 화면 구성(내부 또는 외부)
3. 사용자 인증 정보 → 사용자 인증 정보 만들기 → **OAuth 클라이언트 ID**
→ 애플리케이션 유형 **데스크톱 앱** → 클라이언트 ID / 보안 비밀 확인
실행 (개발 PC, 브라우저 있는 환경에서)
python scripts/google_sheets_authorize.py --client-id <ID> --client-secret <SECRET>
또는 환경변수로:
GOOGLE_SHEETS_OAUTH_CLIENT_ID / GOOGLE_SHEETS_OAUTH_CLIENT_SECRET
브라우저가 열리면 **시트를 편집할 수 있는 계정**으로 로그인·동의한다.
마지막에 출력되는 refresh token 을 서버 `.env` 의
`GOOGLE_SHEETS_OAUTH_REFRESH_TOKEN` 에 넣는다.
주의
- 출력된 refresh token 은 비밀값이다. Git·메신저에 남기지 말 것.
- 토큰은 발급한 사용자 권한으로 동작한다. 그 사용자가 대상 문서를 편집할 수
있어야 하며, 계정 비밀번호 변경·토큰 폐기 시 다시 발급해야 한다.
"""
from __future__ import annotations
import argparse
import os
import sys
SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
def main() -> int:
parser = argparse.ArgumentParser(description="Google Sheets refresh token 발급")
parser.add_argument("--client-id", default=os.getenv("GOOGLE_SHEETS_OAUTH_CLIENT_ID", ""))
parser.add_argument(
"--client-secret", default=os.getenv("GOOGLE_SHEETS_OAUTH_CLIENT_SECRET", "")
)
parser.add_argument(
"--port", type=int, default=0, help="로컬 콜백 포트(기본: 임의 포트)"
)
args = parser.parse_args()
client_id = (args.client_id or "").strip()
client_secret = (args.client_secret or "").strip()
if not client_id or not client_secret:
print("client id / secret 이 필요합니다. --client-id / --client-secret 또는 환경변수로 넣으세요.")
return 2
try:
from google_auth_oauthlib.flow import InstalledAppFlow
except ImportError:
print("google-auth-oauthlib 가 필요합니다: pip install google-auth-oauthlib")
return 2
flow = InstalledAppFlow.from_client_config(
{
"installed": {
"client_id": client_id,
"client_secret": client_secret,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"redirect_uris": ["http://localhost"],
}
},
scopes=SCOPES,
)
# prompt=consent 를 줘야 refresh token 이 항상 내려온다.
creds = flow.run_local_server(port=args.port, prompt="consent", access_type="offline")
if not creds.refresh_token:
print("refresh token 을 받지 못했습니다. 계정의 기존 권한을 해제하고 다시 시도하세요.")
return 1
print()
print("=" * 70)
print("서버 .env 에 아래 값을 넣으세요 (비밀값 — 공유 금지)")
print("=" * 70)
print(f"GOOGLE_SHEETS_OAUTH_CLIENT_ID={client_id}")
print("GOOGLE_SHEETS_OAUTH_CLIENT_SECRET=<위에서 쓴 보안 비밀>")
print(f"GOOGLE_SHEETS_OAUTH_REFRESH_TOKEN={creds.refresh_token}")
print("=" * 70)
return 0
if __name__ == "__main__":
sys.exit(main())