재가입 제한 기간 설정으로 플랫폼 어뷰징 방지 기능 추가
목차
탈퇴 직후 재가입을 완전히 열어두면 어떻게 되냐면 - 어뷰징에 사실상 무방비가 된다. 가입 보너스를 긁어먹고, 탈퇴하고, 다시 가입하고. 계정 자체가 일회용이 되는 상황. 그게 반복되면 관리 비용이 늘고, 나중에 정책을 강화하려 할 때 기존 데이터를 소급해서 처리해야 하는 더 큰 문제가 생긴다. 처음부터 막을 수 있으면 그게 낫다.
이번에 추가한 건 탈퇴 후 일정 기간 동안 같은 계정으로 재가입을 막는 정책이다. 플랫폼 어뷰징 방지가 목적이고, 기간은 관리자가 시스템 설정 화면에서 직접 조정할 수 있도록 했다. 하드코딩이 아니라 설정값으로 뺀 이유는 간단함 - 운영하면서 적정 기간이 뭔지는 직접 써봐야 알기 때문이다. 처음엔 30일이 맞다고 생각했다가 60일이 더 적절하다고 판단이 바뀔 수 있고, 특정 이벤트나 정책 변경 시점엔 제한을 조정해야 할 수도 있다. 그걸 배포 없이 바꿀 수 있어야 하는 건 기본이다.
기간을 0으로 설정하면 제한 없이 재가입 가능한 상태로 동작하도록 처리했다. 엣지 케이스지만 "제한 없음"을 명시적으로 표현할 수 있어야 하고, null과 0을 혼용하면 조건 분기를 짜는 쪽에서 혼란이 생기니까 0을 기준으로 통일했다. 관리자 화면에서 변경하면 즉시 적용되기 때문에 캐시 처리 같은 거 별도로 신경 쓸 필요는 없었다.
가입 흐름에서 체크가 들어가는 위치
회원가입 요청
→ 재가입 제한 기간 체크
→ 약관 동의 확인
→ 회원 생성
→ 동의 이력 저장
체크를 맨 앞에 두는 게 맞다. 약관 동의 처리나 회원 생성 로직을 다 돌리고 나서 제한 기간에 걸렸다고 에러를 던지는 건 낭비고, 사용자 경험 관점에서도 이상하다. 제한에 걸린 사람이라면 그걸 가장 먼저 알려줘야 한다. 뒤늦게 알려주는 구조는 서버도 괜히 일하고, 유저도 뭔가 처리가 됐다가 거부됐다는 느낌을 받게 된다.
약관 동의는 서비스 이용약관과 외부 결제대행사 약관을 함께 처리한다. 여기서 동의 이력을 DB에 저장하는 게 단순히 "동의했냐 안 했냐"의 플래그 문제가 아니다. 언제, 어떤 버전의 약관에 동의했는지 기록이 남아야 나중에 법적 근거로 쓸 수 있다. 약관이 개정됐을 때 기존 사용자에게 재동의를 받아야 하는 시점도 이 이력 데이터를 기준으로 판단하게 된다. 저장 타이밍을 회원 생성과 같은 트랜잭션 안에 묶어야 하는 것도 이 이유다 - 회원은 생겼는데 동의 이력이 없거나, 반대로 이력만 있고 회원이 없거나 하는 상태는 나중에 추적이 어려워진다.
Spring MVC + MyBatis 환경에서 챙긴 것들
스택은 Spring MVC + MyBatis고, 쿼리는 SQL XML로 관리하고, 뷰는 JSP다. 이 구조 안에서 잘 동작하는 코드를 작성하는 게 목표였다. 트랜잭션 범위, 예외 처리 방식, 로그 전략 이 세 가지를 주로 챙겼다.
트랜잭션은 회원 생성 INSERT와 동의 이력 저장 INSERT를 하나로 묶었다. 조회만 하는 제한 기간 체크는 트랜잭션 밖에서 처리해도 무방하다. DB를 쓰는 로직이 여러 단계에 걸쳐 있을수록 어노테이션 범위가 어디까지 커버하는지를 명확히 해야 한다. 암묵적으로 "어딘가에서 처리되겠지"하고 두면 나중에 장애 상황에서 부분 커밋 문제가 생긴다.
예외 처리는 비즈니스 규칙에 의한 거절과 시스템 에러를 분리했다. 재가입 제한에 걸린 케이스는 시스템이 뭔가 잘못된 게 아니라 정책상 거부하는 것이기 때문에, RuntimeException을 그냥 위로 올리는 것보다 별도의 비즈니스 예외 클래스로 분리해서 컨트롤러에서 명시적으로 잡아 처리하는 게 낫다고 판단했다. 에러 로그에 섞이면 나중에 실제 장애를 구분하기가 힘들어진다.
로그는 INFO 레벨로 중요 입출력값을 남겨뒀다. 운영 중에 "왜 이 사람 가입이 안 되냐"는 문의가 들어왔을 때 로그만 보고 원인을 파악할 수 있어야 한다. 재가입 제한에 걸렸다는 걸 로그 없이 DB 직접 조회로만 추적해야 한다면 운영 비용이 불필요하게 올라간다.
| 항목 | 선택 | 이유 |
|---|---|---|
| 트랜잭션 범위 | 회원 생성 + 동의 이력 묶음 | 부분 커밋 방지, 데이터 정합성 |
| 제한 기간 0 처리 | 제한 없음으로 동작 | null 혼용 시 분기 혼란 방지 |
| 예외 타입 | 비즈니스 예외 별도 분리 | 시스템 에러와 정책 거절 구분 |
| 로그 레벨 | INFO로 입출력 기록 | 운영 문의 대응 및 추적 가능성 |
코드 리뷰 단계에서 주요 체크포인트는 기존 가입 흐름에 영향이 없냐는 거였다. 새 로직이 중간에 들어가면 기존 케이스가 의도치 않게 튕길 수 있다. 특히 체크 로직이 예외를 던지는 방식이라면 그 예외가 어디서 어떻게 처리되는지 전체 흐름을 따라가면서 확인했다. MyBatis 환경에서는 매퍼 인터페이스와 SQL XML 사이의 연결도 실수가 발생하기 쉬운 지점이라 거기도 한 번 더 봤다.
작업 후엔 로컬에서 기동 확인, 개발 DB 반영, 배포 순서로 처리했다. 작업 규모 자체는 크지 않았지만 회원 가입이라는 핵심 흐름에 손을 댄 거라 확인 단계를 꼼꼼하게 밟았다. 이런 류의 정책 로직은 동작 안 하면 바로 민원으로 돌아오고, 너무 강하게 막으면 정상 사용자가 튕기고, 너무 약하면 어뷰징을 못 막는다. 설정값으로 뺀 게 다행이라고 생각한다. 운영하면서 조정할 여지를 남겨두는 게 이런 정책 기능에서는 거의 필수에 가깝다.
댓글 0
첫 댓글 달아줘.