📚 엔지니어 · 세션 · LLM · MCP · Core (수정중) · L04

L33 — Google 리소스는 "누구 계정으로" 읽나: 공용 계정 vs 사용자별 OAuth (수정중)

작성일자 2026-07-20  ·  수정일자 2026-07-20  ·  강의차수 L04  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 새 기능에서 "이건 공용 계정 하나로 충분한가, 사용자별 OAuth 가 필요한가"를 "누구 계정으로 그 리소스를 읽나" 라는 한 질문으로 판단할 수 있다.

1. 오늘의 한 문장

Google Docs 든 Drive 든 Sheets 든, 우리 서버가 그걸 읽으려면 누군가의 계정으로 읽어야 한다. 그리고 Google 은 "그 계정이 그 문서에 접근 권한이 있나"로 허용/거부를 정한다.

그래서 오늘 배울 건 API 사용법이 아니라 설계 판단 하나다:

이 기능은 어느 한 계정으로 읽어도 되나, 아니면 사용자마다 자기 계정으로 읽어야 하나?

이 질문을 놓치면, 개발할 땐 멀쩡하다가 실제 사용자가 쓸 때 갑자기 "권한 없음"으로 막힌다. 왜 그런지가 오늘의 이야기다.


2. 지금까지 잘 돌던 방식 — 공용 계정 하나

우리 프로젝트들은 보통 Google 을 계정 하나로 읽는다. docs/google_oauth_setup.md 를 보면, Cloud Console 에서 데스크톱 앱 타입으로 토큰(그 계정으로 접근해도 된다고 증명하는 열쇠 파일)을 한 번 만들어 credentials/token.json 에 두고, 이후 모든 요청이 그 계정으로 나간다.

이게 오래 문제없이 돌아간 이유는, 읽는 대상이 조직이 공용으로 쓰는 자료였기 때문이다. 회사 공용 스프레드시트, 팀이 공유한 폴더 — 이런 건 그 공용 계정에 권한이 있으니 잘 읽힌다. 사용자는 인증에 아예 관여하지 않는다. 개발자가 토큰을 한 번 만들어 서버에 넣으면 끝이다.

여기까지는 좋다. 공용 자료를 읽는 자동화라면 이 방식이 정답이다.


3. 벽에 부딪히는 순간

문제는 앱이 사용자 본인이 소유한 자료를 읽어야 할 때 터진다.

실제로 한 프로젝트(회의록 시스템)에서 이런 일이 있었다. 사용자가 자기가 만든 회의 녹화 문서로 작업을 돌렸는데, 이런 메시지가 떴다:

권한 없음(Permission denied). 이 문서를 당신의 Google 계정에 공유했는지 확인하세요.

본인 문서인데 왜 "권한 없음"일까? 서버는 그 문서를 사용자 계정이 아니라 공용 계정으로 읽으러 갔기 때문이다. 사용자 개인 소유의 비공개 문서는 그 공용 계정에 공유돼 있지 않으니, Google 입장에선 "모르는 계정이 남의 비공개 문서를 달라는" 요청이라 거부한 것이다.

여기서 오늘의 두 가지 핵심을 잡자.

  • 범인은 "권한 스코프"가 아니다. 토큰이 Docs·Drive 를 읽을 권한(스코프)은 다 갖고 있었다. 문제는 "누구 계정이냐" — 문서에 접근 권한을 가진 사람이 그 계정이 아니었다. (403(권한 없음이라는 뜻의 에러 번호)을 보고 스코프부터 의심하면 헛다리다.)
  • 에러 문구가 사람을 속인다. "your Google account 에 공유하라"는 말의 "your" 는 사실 사용자 본인이 아니라 서버의 공용 계정이었다. 문구를 곧이곧대로 읽으면 원인을 영영 못 찾는다.

참고로 403 은 원인이 늘 같지 않다 — 이번엔 "누구 계정이냐"였지만, 같은 403 이 CSRF 나 인증 헤더 때문일 때도 있다(L23). 증상(403)과 원인을 붙여서 단정하지 않는 습관이 여기서도 통한다.


4. 그래서 갈림길 — "누가 동의하느냐"

이 벽을 넘는 유일한 길은, 그 문서를 소유한 사용자 본인 계정으로 읽는 것이다. 그러려면 사용자가 "내 Google 계정으로 읽어도 좋다"고 직접 동의해줘야 한다.

바로 이 지점이 데스크톱 앱 방식과 웹 애플리케이션 방식이 갈리는 진짜 이유다. 둘의 차이를 "리다이렉트 URI(동의 후 브라우저를 돌려보낼 주소) 가 어떻고" 같은 설정으로 외우지 말고, 한 가지로 기억하자 — 누가 동의하느냐.

공용 계정 하나로 다 읽기 — 데스크톱 앱 토큰 (사용자는 관여 안 함)서버공용 계정 1개조직 공용 문서 → 읽힘 ✓사용자가 소유한 비공개 문서 → 403 ✗ (그 벽)사용자별 계정으로 읽기 — 웹앱 OAuth (사용자가 최초 1회 직접 동의)서버사용자별 자격A 가 소유한 문서 → 읽힘 ✓A 계정으로B 가 소유한 문서 → 읽힘 ✓B 계정으로

  • 데스크톱 앱 / 공용 계정: 개발자가 계정 하나로 토큰을 만든다. 사용자는 동의에 관여하지 않는다. → 조직 공용 자료 자동화에 맞다.
  • 웹 애플리케이션 / 사용자별: 브라우저에서 사용자 각자가 "내 계정 연동"에 동의한다. 이후 요청은 그 사람 본인 신원으로 나간다. → 사용자가 소유한 자료를 대신 읽어야 할 때 필수다.

우리 education 사이트도 실은 둘 다 쓰고 있다 — Gmail/Drive API 는 데스크톱 앱 토큰으로(docs/google_oauth_setup.md), 로그인(Sign in with Google)은 웹 클라이언트로(docs/google_signin_setup.md). 다만 education 의 웹 클라이언트는 "이 사람이 누구인지"만 확인(로그인)하고, "이 사람 대신 문서를 읽는" 데까지는 쓰지 않는다. 그 한 발을 더 나간 게 §3 의 회의록 사례다.


5. 사용자별로 바꾸면 생기는 현실 — 그리고 함정

방향은 정해졌다. "사용자가 최초 1회 브라우저에서 동의 → 이후 그 사람 신원으로 자동" 이다. 사용자 경험은 오히려 자연스러워진다. "내 계정으로 내 문서를 읽는" 당연한 모델이 되고, 더는 문서를 공용 계정에 수동으로 공유할 필요가 없다.

대신 구현할 때 브라우저에서만 드러나는 함정이 몇 개 있다. 공통점은 전부 "설정·기본값" 문제라 유닛테스트(코드 조각을 자동으로 검사하는 것)로는 절대 안 걸리고, 실제 브라우저로 눌러봐야 나온다는 것이다. 세 가지만 감으로 알아두자:

  1. 도메인을 등록 안 해서 팝업이 아예 안 열림. 웹 클라이언트에는 이 앱이 돌 도메인(dev·prod 각각)을 미리 등록해줘야 한다.
  2. 동의까지 눌렀는데 아무 일도 안 일어남. 가장 헷갈리는 증상이다 — 에러도 로그도 없다. 브라우저 팝업이 원래 창에 결과를 돌려주는 통신을, 웹 프레임워크(웹사이트를 떠받치는 밑바탕 도구)가 기본으로 켜 두는 보안 설정이 조용히 막아서 그렇다.
  3. 이미 넓은 권한을 승인해 둔 계정이라 스코프가 안 맞음. 그 계정이 예전에 더 많은 권한을 동의해 둬서, 요청한 것과 돌아온 것이 달라 교환이 실패한다.

셋 다 "그럴 수도 있겠다"고 알고만 있으면, 막혔을 때 30분 헤맬 걸 5분에 푼다. 정확한 설정 키·해결값은 팀 wiki 의 lessons/google/ 교훈 두 편(user-owned-resource / gis-popup-oauth)과 popax docs/issues/47_... 에 있으니, 실제로 붙일 때 그걸 보면 된다.

교훈 하나 더: OAuth 팝업 기능은 반드시 실브라우저로 검증한다. "코드는 맞는데 왜 안 되지"의 상당수가 이 세 함정이고, 증상이 안 보일수록 추측 말고 실제로 눌러 증거를 봐야 한다(L24).


6. 전부 갈아엎지 말 것 — 읽기와 쓰기를 나눠 판단

마지막이 오늘 가장 실용적인 부분이다. "사용자별 OAuth 가 필요하다"는 결론이 나도, 모든 Google 호출을 사용자별로 바꾸라는 뜻이 아니다.

같은 프로젝트 안에서도 목적에 따라 갈린다:

  • 읽기 — 사용자가 소유한 자료(본인 회의 문서 등) → 사용자별 OAuth. 공용 계정으론 구조적으로 403 이다.
  • 쓰기·정리 — 조직 공용 자료(팀 공용 폴더에 결과 저장 등) → 공용/서비스 계정 유지. 굳이 사용자별로 바꾸면 팀 공용 정리가 깨지고 사용자에게 요구하는 동의만 무거워진다.

그래서 실전 정답은 대개 혼합이다 — 읽기는 사용자별, 쓰기는 공용. 새 기능을 붙일 때마다 §1 의 질문 하나로 돌아오면 된다: "이건 누구 계정으로 읽어야 하나?"


오늘 정리 + 다음

  • 정리: Google 리소스 접근은 "누구 계정으로 읽느냐"가 권한을 정한다. 공용 계정 하나(데스크톱 앱 토큰) 는 조직 공용 자료엔 완벽하지만, 사용자가 소유한 비공개 자료는 그 계정에 공유돼 있지 않아 403 이다(스코프가 아니라 계정의 문제). 그럴 땐 사용자가 직접 동의하는 웹앱 OAuth(사용자별) 로 그 사람 본인 신원으로 읽는다. 다만 전부 갈아엎지 말고 읽기=사용자별·쓰기=공용으로 나눠 판단한다.
  • 흔한 함정: 403 을 보고 스코프부터 의심하기. 스코프가 멀쩡해도 "누구 계정이냐" 때문에 막힌다. 그리고 사용자별 OAuth 팝업의 함정들은 실브라우저 전엔 안 보인다 — 특히 "동의는 됐는데 아무 반응 없음"은 에러조차 없어 유닛테스트만 믿으면 못 잡는다.
  • 다음 시간: 실제로 붙이는 쪽 — 사용자 동의를 받아 그 자격을 저장하고, 비동기 작업에서까지 그 사람 신원으로 안전하게 쓰는 구현(그리고 standarda-core 가 이를 어떻게 거들었는지, L14 처럼 core 에 올라간 기여).
  • 자습 권장: 자기 프로젝트에서 Google 을 읽는 코드 한 곳을 골라 "이건 누구 계정으로 실행되나"를 짚어본다. 사용자 소유 자료를 공용 계정으로 읽고 있으면 잠재적 403 후보다. 대비 자료로 education 의 docs/google_oauth_setup.md(데스크톱)와 docs/google_signin_setup.md(웹)를 나란히 읽어보면 두 방식의 차이가 손에 잡힌다.
이 강의를 학습하셨나요?