
블로그 테마에 API 키를 그대로 적어 두면, 방문자가 브라우저의 ‘소스 보기’만 눌러도 그 키가 보입니다. 제가 만든 지원금 정보 페이지가 정확히 그 상태였습니다. 이 글은 키를 서버 쪽으로 옮기고 블로그에서는 결과만 받아오도록 바꾼 과정입니다.
왜 그렇게 됐나
공공데이터포털 API는 주소 뒤에 serviceKey를 붙여서 호출합니다. 블로그에서 자바스크립트로 부르려면 그 주소를 테마 안에 적어야 하는데, 테마는 방문자에게 그대로 내려갑니다. 숨긴다고 변수 이름을 바꿔봐야 소용없습니다. 결국 브라우저가 그 주소로 요청을 보내야 하니까요.
처음엔 이걸 문제로 인식하지 못했습니다. 화면이 잘 나오니 끝난 줄 알았습니다. 나중에 배포된 페이지에서 소스 보기를 눌러 보고서야 키가 그대로 적혀 있는 것을 알았습니다.

중간에 한 칸을 넣는다
해법은 단순합니다. 브라우저가 API를 직접 부르지 않게 하는 것입니다. 중간에 서버를 하나 두고, 키는 그 서버에만 두고, 블로그는 그 서버만 부릅니다.
서버를 따로 빌릴 필요는 없었습니다. 구글 앱스 스크립트가 무료로 웹앱을 띄워 줍니다. 키는 코드에 적지 않고 스크립트 속성에 넣습니다.
var API_BASE = 'https://api.odcloud.kr/api/gov24/v3';var PER_PAGE = 1000;function getKey_() { var key = PropertiesService.getScriptProperties() .getProperty('BENEFIT_API_KEY'); if (!key) throw new Error('스크립트 속성에 BENEFIT_API_KEY가 없습니다.'); return key;}function fetchPage_(page) { var url = API_BASE + '/serviceList' + '?page=' + page + '&perPage=' + PER_PAGE + '&returnType=JSON' + '&serviceKey=' + encodeURIComponent(getKey_()); var res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); if (res.getResponseCode() !== 200) throw new Error('API ' + res.getResponseCode()); return JSON.parse(res.getContentText());}키를 코드가 아니라 스크립트 속성에 두는 게 핵심입니다. 코드를 남에게 보여줘도 키는 따라가지 않습니다.
덤으로 얻은 것
중간에 서버가 생기니 결과를 저장해 둘 자리도 같이 생겼습니다. 지원금 정보는 하루에 몇 번씩 바뀌는 자료가 아닙니다. 그래서 받아온 결과를 6시간 동안 캐시에 두고, 그 안에 들어온 요청은 저장해 둔 값을 바로 돌려줍니다.
방문자마다 수천 건을 새로 받아오던 것이 사라졌습니다. 보안 때문에 시작한 작업인데 속도가 같이 좋아졌습니다.

적용 순서
- 키를 먼저 새로 발급받습니다. 이미 노출된 키는 그대로 두면 의미가 없습니다.
- 앱스 스크립트를 배포하고 주소를 확인합니다. 브라우저 주소창에 그 주소를 입력해 데이터가 오는지 봅니다.
- 테마를 고치기 전에 기존 테마를 내려받아 둡니다. 되돌릴 자리를 만들어 두는 겁니다.
- 테마에서는 주소 한 줄만 바꿉니다. 화면을 그리는 부분은 건드리지 않습니다.
마지막으로 배포된 페이지를 소스 보기로 열어 serviceKey라는 글자가 안 나오는지 확인하면 끝입니다.
아직 안 된 것
앱스 스크립트 웹앱 주소 자체는 여전히 공개됩니다. 누구나 그 주소를 부를 수 있다는 뜻입니다. 키는 가려졌지만 호출량 제한은 아직 걸지 않았습니다. 지금은 방문자가 많지 않아 미뤄 뒀고, 필요해지면 호출 출처를 확인하는 조건을 넣을 생각입니다.
그리고 이 방식은 읽기 전용 API에만 맞습니다. 데이터를 바꾸는 요청까지 이렇게 열어 두면 안 됩니다.
공식 문서로 보는 스크립트 속성과 캐시
구글 앱스 스크립트 공식 문서(PropertiesService)는 속성 저장소를 세 가지로 나눕니다. 어느 저장소에 키를 두느냐에 따라 누가 값을 읽을 수 있는지가 달라지므로, 위에서 쓴 스크립트 속성이 맞는 선택인지 이 표로 확인할 수 있습니다.
| 저장소 | 접근 범위 | 이 글의 용도 |
|---|---|---|
| 스크립트 속성(getScriptProperties) | 그 스크립트의 모든 사용자, 해당 스크립트 안에서만 | API 키 보관에 적합 |
| 사용자 속성(getUserProperties) | 현재 사용자만, 해당 스크립트 안에서만 | 방문자별 설정이 필요할 때 |
| 문서 속성(getDocumentProperties) | 열려 있는 문서·시트·양식의 모든 사용자 | 독립 웹앱에서는 쓸 수 없음 |
같은 문서는 속성을 서로 다른 스크립트끼리 공유할 수 없다고 적고 있습니다. 문서 속성은 스크립트가 구글 파일에 묶여 있거나 애드온으로 게시된 경우에만 쓸 수 있고, 그렇지 않으면 null이 돌아옵니다. 블로그용 독립 웹앱이라면 스크립트 속성이 사실상 유일한 선택입니다.
캐시도 공식 문서(CacheService)에 구분이 있습니다. 스크립트 캐시는 그 스크립트의 모든 사용자가 공유하고, 사용자 캐시는 현재 사용자별로 나뉘며, 문서 캐시는 독립 웹앱에서는 null을 돌려줍니다. 지원금 목록처럼 모든 방문자가 같은 값을 받는 자료라면 스크립트 캐시가 맞습니다. 다만 문서는 캐시된 데이터가 만료 시각까지 남는다고 보장하지 않으며, 모든 읽기에서 null이 돌아올 수 있다고 전제하고 코드를 짜라고 안내합니다. 그래서 캐시에 값이 없으면 API에서 다시 받아오는 경로를 반드시 남겨 두어야 합니다.
웹앱 배포 설정에서 확인할 것
구글 앱스 스크립트의 웹앱 안내에 따르면, 스크립트는 doGet 또는 doPost 함수가 HtmlOutput이나 TextOutput을 돌려줄 때 웹앱이 됩니다. 브라우저에서 주소를 열거나 GET 요청을 보내면 doGet이 실행됩니다. 배포할 때는 ‘다음 사용자로 실행’을 고를 수 있는데, 소유자로 실행하면 방문자가 누구든 스크립트는 소유자 권한으로 돕니다. 키를 스크립트 속성에서 읽는 이 글의 구조는 이 방식을 전제로 합니다. 또 같은 안내는 ScriptApp.getOAuthToken으로 얻은 토큰을 클라이언트로 보내지 말라고 경고합니다. 키뿐 아니라 토큰도 방문자 화면으로 내려가지 않게 해야 한다는 뜻입니다.
테스트할 때는 배포 메뉴의 테스트 배포가 주는 /dev 주소를 쓸 수 있습니다. 이 주소는 편집 권한이 있는 사용자만 쓸 수 있고 항상 마지막으로 저장한 코드를 실행합니다. 공개용 주소와 섞어 쓰지 않도록 구분해 두는 편이 안전합니다.
호출 한도와 캐시의 관계
앱스 스크립트 할당량 문서(2026-09-03 갱신 표기)에 따르면 외부 주소를 부르는 UrlFetchApp 호출은 개인 계정이 하루 20,000회, Google Workspace 계정이 하루 100,000회입니다. 응답 한 건의 크기 한도는 50MB이고, 할당량은 사용자 단위이며 예고 없이 바뀔 수 있다고 적혀 있습니다. 방문자 요청마다 공공데이터포털을 부르는 구조라면 이 한도를 빠르게 소모할 수 있으므로, 앞서 6시간 캐시를 둔 것은 속도뿐 아니라 한도 관리에도 의미가 있습니다.
정리하면 안전한 구성은 세 겹입니다. 첫째, 키는 코드가 아니라 스크립트 속성에 둡니다. 둘째, 브라우저는 앱스 스크립트 웹앱 주소만 부르고 그 주소는 읽기 전용 결과만 돌려줍니다. 셋째, 같은 결과는 스크립트 캐시에 두어 호출 횟수를 줄입니다. 셋 중 하나라도 빠지면 키 노출, 한도 초과, 느린 응답 가운데 하나가 다시 생깁니다. 적용한 뒤에는 배포된 페이지의 소스 보기에서 serviceKey가 보이지 않는지, 캐시를 비운 첫 요청도 정상으로 나오는지 두 가지를 꼭 확인하세요.
키가 이미 노출됐다면
GitHub 공식 문서(API 자격 증명 안전하게 관리하기)는 토큰·키·앱 시크릿을 코드에 직접 적지 말라고 안내하고, 자격 증명을 비밀번호처럼 다루라고 합니다. 이미 노출된 경우의 처리 순서도 제시합니다. 새 자격 증명을 만들고, 이전 것을 쓰던 모든 곳을 새것으로 바꾸고, 노출된 이전 것을 삭제합니다. 이 글의 적용 순서에서 키를 먼저 새로 발급받으라고 한 이유와 같은 흐름입니다.
| 상황 | 해야 할 일 |
|---|---|
| 키가 테마·코드에 적혀 있음 | 서버 쪽 스크립트 속성으로 옮기고 테마에서는 주소만 남깁니다. |
| 이미 소스 보기로 보였음 | 새 키를 발급받아 교체하고 이전 키를 삭제합니다. |
| 캐시가 비어 있음 | API를 다시 호출하는 경로가 있어야 합니다. |
| 호출이 많아짐 | 캐시 시간을 늘리고 일일 호출 한도를 확인합니다. |
확인 자료
- Google Apps Script: PropertiesService
- Google Apps Script: CacheService
- Google Apps Script: 웹앱 만들기
- Google Apps Script: 할당량과 한도
- GitHub Docs: API 자격 증명 안전하게 관리하기
기준일: 2026-10-18