마을 통합 로그인 붙이기 — 입장권 확인, 서명형 세션 표, iframe 허용 제한
같은 도메인의 그래픽 채팅 마을 VUTOPIA 에서 게임으로 넘어올 때, 마을 계정으로 바로 들어오게 하는 작업을 했다. 마을이 발급한 입장권을 VARENA 서버가 확인하고, 확인되면 이름 뒤에 인증 표시를 붙인다. 오후에는 마을 안 패널에 게임을 띄울 수 있게 iframe 허용 범위를 정했고, 마지막으로 게임 세션 표를 서버가 기억하지 않아도 되는 서명형으로 바꿨다.
한 일
마을 입장권으로 들어오기
- 마을에서 게임으로 넘어올 때 주소에 입장권이 붙어 온다. 화면은 이걸 읽자마자 주소에서 지운다(방 코드·게임 방식 같은 다른 값은 그대로 둔다)
- 입장 화면의 이름칸은 마을 닉네임으로 잠기고 “마을 계정 ✔” 표시가 나온다
- 서버가 입장권을 확인하면 이름 뒤에 “ ✔” 를 붙인다. 일반 닉네임에 체크 표시 비슷한 글자를 넣어 흉내 내지 못하게, 일반 이름에서는 그런 글자를 지운다
- 인증이 안 되면 다시 시도하지 않고 일반 닉네임으로 이어 가며 짧은 안내만 띄운다
- 입장권과 세션 표는 로그에 남기지 않는다. 페이지에는
no-referrer를 걸어 주소가 다른 곳으로 새지 않게 했다
게임 현황 API
- 지금 접속자 수와 방 수를 돌려주는 작은 공개 API 를 만들었다. 캐시하지 않고, IP 별로 호출 횟수를 제한한다
마을 패널에서만 iframe 허용
- 모든 HTTP 응답에
Content-Security-Policy: frame-ancestors를 붙인다. 기본은 자기 자신만이고, 설정한 출처(마을 패널)만 추가로 허용한다 - 설정값의 출처 모양이 틀리면 서버를 시작하지 않는다. 잘못 쓴 설정이 조용히 “아무 데서나 허용”이나 “아무 데서도 안 됨”이 되는 것을 막으려는 것이다
세션 표를 서명형으로
처음에는 입장권을 확인한 뒤 서버 메모리에 세션 표(12시간)를 두었다. 그런데 이러면 배포로 서버를 다시 띄울 때마다 표가 사라지고 마을 인증이 풀린다. 그래서 세션 표 자체에 내용과 서명을 담는 방식으로 바꿨다.
기술 메모
입장권 확인은 서버만 한다
화면도 입장권의 내용(이름)을 읽기는 하지만 입장 화면에 보여 주는 용도일 뿐 믿지 않는다. 실제 확인은 서버가 마을과 함께 정한 규격의 순서대로 한다. 대상(aud)이 이 게임인지, 서명이 맞는지, 만료되지 않았는지, 이미 한 번 쓴 입장권(jti)이 아닌지를 본다. base64url 도 정규형인지 확인한다. 같은 바이트를 다른 글자로 쓸 수 있으면 “한 번만 쓰기” 검사를 빠져나갈 여지가 생기기 때문이다.
서명 비교는 Node 의 crypto.timingSafeEqual 로 한다. 이 함수는 길이가 다르면 예외를 던지므로 길이를 먼저 비교한다.
import { createHmac, timingSafeEqual } from 'node:crypto';
function sign(secret: string, data: string): string {
return createHmac('sha256', secret).update(data).digest('base64url');
}
function sameText(a: string, b: string): boolean {
const x = Buffer.from(a);
const y = Buffer.from(b);
return x.length === y.length && timingSafeEqual(x, y);
}
리뷰 뒤에는 서명을 바이트가 아니라 base64url 글자 그대로 비교하도록 맞췄다. 마을·게임 등 이 규격을 쓰는 저장소들이 같은 문구로 같은 비교를 하게 하기 위해서다.
메모리 세션 표 → 서명형 세션 표
서명형 세션 표는 접두어.내용.서명 모양이다. 내용에는 대상·사용자·이름·만료 시각이 들어 있고, 서명은 접두어와 내용을 함께 HMAC-SHA256 으로 묶은 값이다. 서버는 표를 저장하지 않고 들어올 때마다 서명과 만료를 다시 확인한다. 같은 비밀을 쓰는 한 서버를 다시 띄워도 표가 그대로 통한다.
- 접두어에 버전을 넣어, 옛 메모리 방식 표나 다른 버전 표는 섞여 들어오지 않게 거절한다(거절되면 일반 입장 + 안내)
- 입장권의 “한 번만 쓰기” 기억은 그대로 둔다. 세션 표가 서명형이 됐다고 입장권 재사용 검사까지 없앨 수는 없다
- 테스트에 “서버를 새로 만든 뒤에도 표가 통하는지”와 만료·변조·대상·버전 섞기 거절을 넣었다
화면 쪽은 받은 세션 표를 sessionStorage 에 둔다. 탭 안에서만 살아 있어서 새로고침·재접속·다음 판에는 다시 쓰고, 탭을 닫으면 사라진다. 저장이 막힌 브라우저라면 다시 들어올 때 일반 닉네임이 된다.