GRG Games 랭킹 SDK.
masonfe-hyunsu
🧵 1. 개요.
GRG Games는 브라우저에서 바로 도는 HTML5 미니게임(설치 없이 웹에서 짧게 즐기는 작은 게임)을 한곳에 모아 둔 플랫폼이다.
목록에서 게임을 선택하면, 브라우저에서 바로 플레이되고, 점수 있는 게임은 기록이 랭킹에 남는다.
그 개인 랭킹을 등록하려고, 게임 제작자가 쓰기 편하게 JS SDK를 제공할 수 있게 준비했다.

게임 HTML에는 스크립트랑 종료 훅만 남기고, 인증이랑 검증은 플랫폼이 맡는다. 연동은 개발자 가이드에 정리해 뒀다.
이 포스팅에서는 그 랭킹 SDK랑 보안 경계를 정리해 본다.
한 줄로 요약하면, 점수는 게임이 아니라 플랫폼이 연 플레이 세션(한 판을 서버가 식별하는 기록)으로 받는다.
🎯 2. 간편한 SDK 구성.
제작자가 개인 랭킹을 붙일 때, 게임이 끝나면 점수만 넘기면 끝나게 짜고 싶었고, 그래서 구성은 스크립트 한 줄이랑 종료 훅 하나다.
스크립트 한 줄:
https://grggames.com/grg-games-sdk.js종료 훅 하나:
GRGScore.onGameEnd(score)점수 타입:
Number만. 문자열,Infinity,NaN,null은 막는다허용 범위:
-999,999~999,999,999,999
골프 타수나 클리어 시간처럼 낮은 숫자가 더 좋은 게임도 있다. 그런 경우는 최종 score에 * -1을 붙여 음수로 넘기라고 했다.
랭킹 정렬은 항상 “큰 값이 위”로 두고, 규칙 차이는 부호로 흡수하는 쪽이 단순했다.
<script src="https://grggames.com/grg-games-sdk.js"></script>// 게임 종료 시 Number 타입 score 제출
GRGScore.onGameEnd(score);게임 로직은 게임 쪽에, 스코어 파이프라인(점수 검증, 인증, 전송, 랭킹 반영)은 플랫폼 쪽에 둔다.
게임 HTML은 iframe으로 띄우고, 점수는 부모 페이지(게임을 감싼 호스트)가 플레이 세션으로 넘긴다.

🛡️ 3. 클라이언트 조작과 보안.
점수는 브라우저에서 올라오니 클라이언트 조작을 많이 고민했고, 서버가 그 값만 믿고 가면 안 됐다.
그래서 게임에 넘기던 토큰(클라이언트가 들고 다니는 인증값)을 iframe에 주지 않기로 했고, 브라우저에 들어간 비밀값은 탈취된다고 봤다.
그래서 경계를 이렇게 나눴다.
게임:
GRGScore.onGameEnd(score)로 점수만 보내고, 토큰은 읽거나 전달하지 않는다.부모 페이지(게임을 iframe으로 감싼 호스트): 로그인 유저 기준으로 서버에 플레이 세션(한 판을 서버가 식별하는 기록)을 연다. 세션 ID는 부모와 서버에만 두고, 게임 iframe URL,
localStorage, DOM에는 넣지 않는다.서버:
POST /api/scores만 점수를 받는다.player_scores직접 write는 막았다.
클라이언트 SDK만으로 치트를 끝낼 수는 없다.
이상한 타입, 말도 안 되는 값, 세션 없는 제출부터 걸러 둔다. 다만 점수를 클라이언트가 정해서 보내는 구조라, 로그인한 사람이 세션만 연 뒤 가짜 점수를 넣는 것까지는 막지 못한다.
⏱️ 4. Rate Limit과 Cross-Origin.
같은 세션에서 점수를 연타하거나, 게임이 아닌 곳에서 점수를 직접 쓰는 것도 막아야 했다.
예전엔 로그인만 되면 DB에 upsert(있으면 갱신, 없으면 삽입)가 가능했다.
그래서 Rate Limiting(짧은 시간에 같은 요청을 너무 자주 못 보내게 막는 제한)이랑 출처 검사를 같이 걸었다.
속도: 1초당 1회
일일 상한: 100회
초과 시: 거절한다
제한은 SDK만이 아니라 서버
play_sessions에서도 본다세션 시작 후 약 2초 안에는 제출이 안 된다 (
min_play_ms, 게임별로 더 늘릴 수 있음)게임별
max_score가 있으면 그 상한을 넘긴 점수는 거절한다
Cross-Origin 보안(허용된 출처에서 온 요청인지 보는 경계)도 같이 묶는다.
부모가 받는 건 게임이 호스팅된 도메인에서 온
postMessage뿐이다부모 페이지 origin에서 DevTools로
postMessage를 보내는 위조는 받지 않는다실제 DB 기록은 서버 API + 본인 세션이 있을 때만 된다

📐 5. 이 구성의 한계.
SDK는 붙이기 쉽게 짰다. 조작이 그냥 된다는 뜻은 아니고, 막는 것과 못 막는 것이 갈린다.
개발자 가이드도 SDK를 넣고 onGameEnd만 호출하면 끝나게 짧게 잡아 뒀다.
막는 것과 못 막는 것을 나눠 보면 이렇다.
점수는 게임이 보낸 값을 받는다. 서버가 플레이를 다시 보고 가짜를 걸러 내진 않아서, 로그인한 사람이 세션을 연 뒤 점수를 부풀리는 것까지는 막지 못한다.
인증값을 게임에 넣으면 빼갈 수 있어서, 세션은 부모 페이지와 서버에만 둔다. 게임에서 쓰는 API는 그대로라 이미 붙인 게임은 고칠 필요가 없다.
점수가 진짜인지까지 보려면, 같은 조작이면 결과가 같은 게임만 골라 서버에서 다시 돌려 봐야 한다. HTML5 iframe에 올린 게임을 전부 그렇게는 못 한다.
그래도 점수를 부풀리면 티가 나는 지점은 상위 1~7등 보상이랑 홈의 Top Player 목록이다. 게임마다 점수 상한은 이미 있고, 비정상적으로 높은 점수는 나중에 보류하는 쪽이 다음이다.
요청을 너무 자주 보내는 건 Rate Limit으로 줄인다. 다만 재도전을 많이 하는 장르면 하루 상한을 다시 봐야 한다.
이상한 값, 세션 없는 제출, 게임 밖에서 DB를 직접 쓰는 건 막는다. 이번 서버 세션 업데이트로 막은 것도 게임에서 훔친 값으로 DB에 쓰는 길이다. 로그인한 사람이 세션을 연 뒤 점수를 부풀리는 것까지는 이번에도 못 막는다. HTML5 미니게임 랭킹에서는 그 선을 일단 받아들였다.
댓글