MSG CTF
건국대(seKUrity) · 세종대(SSG) · 명지대(MJSEC)가 함께 운영한 MSG CTF 대회 플랫폼입니다. CTFd를 사용하지 않고 Spring Boot 백엔드와 React 프론트엔드, Discord Bot을 직접 구성해 대회 운영에 필요한 회원, 문제, 점수, 관리자 기능을 구현했습니다. 이 프로젝트에서는 주로 User API와 인증 흐름을 담당했습니다.
담당 역할
백엔드 팀에서 User API를 중심으로 회원가입, 로그인, 로그아웃, 토큰 재발급, 이메일 인증, 프로필 조회 흐름을 구현했습니다. 초기 인증 구조를 잡는 단계였기 때문에 Spring Security 필터 체인, JWT Access/Refresh Token 분리, Refresh Token 저장/삭제, Access Token 블랙리스트 처리까지 직접 다뤘습니다. 추가로 문제 전체 조회, 문제 상세 조회, solvers 필드 노출 같은 Challenge 조회 API 일부를 구현했습니다.

Tech Stack
아키텍처 / ERD
클라이언트는 Spring Boot API로 로그인/회원가입 요청을 보내고, 로그인 성공 시 Access Token은 Authorization 헤더로, Refresh Token은 HttpOnly Cookie로 전달됩니다. JWT 필터는 Access Token의 만료 여부, tokenType, 블랙리스트 여부를 검증한 뒤 SecurityContext에 loginId와 role을 설정합니다. 이메일 인증 코드는 Redis에 5분 TTL로 저장하고, 인증 성공 여부는 30분 TTL로 보관해 회원가입 단계에서 확인했습니다. 시스템 아키텍처와 ERD는 Renewal 프로젝트와 같은 자료를 공유합니다.
시스템 아키텍처
README에 정리된 MSG CTF 운영 구조입니다. 백엔드, 프론트엔드, DB, Redis, NGINX, Discord Bot이 함께 동작하는 전체 맥락을 보여줍니다.

ERD
유저, 문제, 제출 기록, 리더보드, 토큰 데이터의 관계를 보여줍니다. 회원 인증과 대회 기록이 어떻게 연결되는지 설명하는 보조 자료로 사용했습니다.

주요 기능
- ✓회원가입 시 ID, 이메일, 비밀번호 형식과 중복 여부를 검증하고, 학교 이메일 도메인만 가입할 수 있도록 제한했습니다. 일반 회원가입 요청에서는 role을 항상 ROLE_USER로 고정해 사용자가 요청 본문으로 관리자 권한을 지정하지 못하도록 했습니다.
- ✓Gmail SMTP로 6자리 이메일 인증 코드를 전송하고, Redis에 인증 코드와 인증 완료 상태를 TTL 기반으로 저장했습니다. 인증 시도 횟수를 제한해 잘못된 코드 반복 입력을 방지했습니다.
- ✓로그인 성공 시 Access Token과 Refresh Token을 분리 발급했습니다. Access Token은 Authorization 헤더로, Refresh Token은 HttpOnly Cookie와 DB에 저장해 토큰 재발급과 로그아웃 흐름을 구성했습니다.
- ✓로그아웃 시 Refresh Token을 삭제하고 Access Token을 블랙리스트에 저장해 같은 Access Token으로 다시 인증되는 문제를 막았습니다. 만료된 블랙리스트 토큰은 스케줄러로 주기적으로 정리하도록 구현했습니다.
- ✓문제 전체 조회와 특정 문제 상세 조회 API를 구현하고, 문제 상세 응답에 solvers 필드를 추가했습니다. 프론트엔드가 문제별 풀이 인원 정보를 함께 표시할 수 있도록 응답 데이터를 보강했습니다.
트러블슈팅
- 회원가입 권한 상승 가능성문제 상황초기 회원가입 요청에서 role 값을 사용자가 직접 전달할 수 있으면, 일반 사용자가 요청 본문에 admin 권한을 넣어 가입을 시도할 수 있었습니다.해결 방안일반 회원가입에서는 요청 role을 신뢰하지 않고 ROLE_USER로 고정했습니다.관리자 계정 생성은 별도 관리자 API 흐름으로 분리해 공개 회원가입과 권한 부여 책임을 나눴습니다.결과외부 사용자가 회원가입 API만으로 관리자 권한을 얻을 수 있는 경로를 차단했습니다.
- 이메일 인증 코드 만료와 반복 시도 문제문제 상황학교 이메일 인증 없이 회원가입이 가능하거나, 인증 코드를 무제한으로 시도할 수 있으면 대회 참가자 검증이 약해질 수 있었습니다.해결 방안인증 코드는 Redis에 5분 TTL로 저장하고, 인증 성공 상태는 30분 TTL로 저장했습니다.잘못된 인증 시도는 email 기준으로 카운트하고 5회를 넘으면 인증 코드와 시도 횟수 키를 삭제하도록 처리했습니다.결과학교 이메일 인증을 회원가입의 필수 단계로 만들고, 만료/오입력/반복 시도 상황을 명확히 제어했습니다.
- 로그아웃 후 Access Token 재사용 문제문제 상황초기 로그아웃 흐름은 Refresh Token 삭제 중심이어서, 이미 발급된 Access Token이 만료 전까지 남아 있으면 같은 토큰으로 인증 요청이 가능할 수 있었습니다.해결 방안로그아웃 시 Access Token을 블랙리스트 테이블에 저장하고, JwtFilter에서 블랙리스트 토큰이면 요청을 거부하도록 수정했습니다.토큰 만료 시간이 지난 블랙리스트 데이터는 30분 주기 스케줄러로 삭제했습니다.결과로그아웃된 Access Token의 재사용을 서버 측에서 차단하고, 블랙리스트 데이터가 무기한 쌓이지 않도록 관리했습니다.
성능 / 안정성
- 이메일 인증 상태와 인증 코드를 Redis TTL로 관리해 DB에 일회성 인증 데이터를 저장하지 않도록 했습니다.
- 만료된 블랙리스트 토큰을 스케줄러로 정리해 로그아웃 토큰 저장소가 계속 커지지 않도록 했습니다.
- Access/Refresh Token을 분리해 짧은 수명의 Access Token과 재발급 가능한 Refresh Token 구조를 만들었습니다.
보안 고려 사항
- 비밀번호는 PasswordEncoder로 해싱해 저장하고, 로그인 시 평문 비교 대신 matches로 검증했습니다.
- Refresh Token은 HttpOnly Cookie로 전달하고 DB에 저장해 재발급과 로그아웃 시 서버 측 검증이 가능하도록 했습니다.
- 학교 이메일 도메인을 제한하고 SMTP 인증 코드를 요구해 대회 참가자 범위를 제어했습니다.
- CORS는 로컬 프론트엔드와 운영 도메인을 기준으로 허용 Origin을 제한했습니다.
아쉬웠던 점
처음 인증 구조를 만들면서 Access Token과 Refresh Token의 역할, 로그아웃 처리, 토큰 블랙리스트의 필요성을 직접 겪었습니다. 초기에는 Refresh Token 삭제만으로 로그아웃을 처리하려 했지만, 리뷰 과정에서 Access Token 재사용 문제가 드러났고 서버 측 블랙리스트 검증을 추가했습니다. 다만 `/api/users/**` 전체 permitAll처럼 개발 편의를 위해 넓게 열어둔 인가 설정은 운영 관점에서 더 세밀하게 분리했어야 합니다. 이 경험 이후 인증 기능은 단순히 로그인 성공 여부가 아니라 토큰 생명주기, 권한 경계, 로그아웃 이후 상태까지 함께 설계해야 한다는 점을 배웠습니다.
프로젝트 유형
팀 프로젝트
기간
2024.12 - 2025.03
완료
2025-04
사용 기술
9 Technologies
다른 프로젝트
CareLink
🏆 2026 GOORM DEEPDIVE HACKATHON 최우수상 외국인 환자를 위한 AI 기반 다국어 의료 커뮤니케이션 플랫폼 (증상 분석, 처방전 번역, 병원 찾기, 커뮤니티)
개인 프로젝트Steam Game Recommendation Application
방대한 Steam 라이브러리 속에서 사용자가 취향에 맞는 게임을 더 쉽게 찾을 수 있도록 만든 데스크탑 추천 앱입니다. 리뷰 50건 이상의 Steam 게임 약 12,000건을 사전 수집해 H2 임베디드 DB에 탑재했고, Electron으로 패키징해 별도 서버 없이 Windows 환경에서 실행할 수 있도록 구성했습니다. 사용자는 태그 선택, Steam 프로필, 최근 플레이 기록, 자유 텍스트 입력을 통해 게임을 추천받을 수 있으며, 게임 선택 세션과 리뷰 맞추기 미니게임도 함께 제공합니다.