-
Notifications
You must be signed in to change notification settings - Fork 1
[의사결정] FE-BE 비 로그인 사용자 식별 전략 #85
Description
요구사항
- 비로그인 사용자는 동아리에 좋아요를 1초마다 클릭할 수 있다.
문제 정의
비로그인 사용자는 서버가 신원을 확인할 수단이 없다. 이 상태에서 좋아요 기능을 제공하면 다음 문제가 발생합니다.
- 동일 사용자가 무제한 좋아요 가능 → 경쟁 의미 없음
- 스크립트로 자동화된 매크로 공격 → 특정 부스 순위 조작
식별 전략 선택 과정
식별 전략: IP + Device ID 조합을 선택했습니다.
| 식별자 | 사용 시 문제 | 조합 시 역할 |
|---|---|---|
| IP | 학교 WiFi는 수천 명이 같은 IP → 정상 사용자 차단 | 매크로/VPN 없는 일반 공격 차단 |
| Device Id | 값을 바꿔서 전송하면 서버가 검증 불가 | 기기 단위 식별 |
| IP + Device Id | 값을 바꿔서 전송하면 서버가 검증 불가 | 둘 다 바꿔야 우회 가능 → 공격 비용 상승 |
Device ID 발급 방식
id 발급을 클라이언트에서 할지, 서버에서 할지 고민을 해보았습니다.
[기각] X-Device-Id 헤더 (클라이언트 생성)
→ curl -H "X-Device-Id: 아무값" 으로 위조 가능
[선택] HttpOnly Cookie (서버 발급)
→ JS 접근 불가, 브라우저가 자동 전송, 위조 불가
클라이언트가 id를 생성해서 헤더로 보내는 방식은 헤더 값을 임의로 바꿀 수 있어 서버 검증이 불가능하다고 생각했습니다. 그래서 서버가 직접 id를 생성하고 HttpOnly Cookie로 발급하면 JS에서 접근이 불가능 해 식별할 수 있다고 생각했습니다.
논의1 reCAPTCHA v3 도입 여부
구글에서 제공하는 reCAPTCHA v3라는 기술은 사용자가 아무것도 하지 않아도 구글이 브라우저 행동 패턴을 분석해 봇 여부를 0~1점으로 채점합니다. 해당 기술을 도입하면 매크로나 해커에 대한 공격을 방어할 수 있을 것 같습니다.
프론트엔드 구현 비용
| 작업 | 설명 |
|---|---|
| 스크립트 로드 | <script src="https://www.google.com/recaptcha/api.js?render=SITE_KEY"></script> 추가 |
| 토큰 발급 | 좋아요 버튼 클릭 시 grecaptcha.execute(SITE_KEY, {action: 'like'}) 호출 |
| 토큰 전송 | 좋아요 API 요청 시 X-Recaptcha-Token 헤더에 토큰 포함 |
도입 시 달라지는 흐름
좋아요 버튼 클릭
→ grecaptcha.execute() 호출 (토큰 발급, 약 100~300ms 소요)
→ POST /api/v1/booths/{id}/likes (X-Recaptcha-Token 헤더 포함)
→ 서버에서 Google 검증 후 처리
해당 부분에 의견 부탁드립니다.
논의2 프론트와 상호작용하는 서버 파이프라인
제가 생각해본 API 작동 절차를 시퀀스다이어그램으로 표현해 보았습니다. 의견이 필요합니다!
sequenceDiagram
participant B as Browser
participant F as DeviceIdCookieFilter
participant S as Spring Server
participant R as Redis
Note over B,S: 좋아요 클릭
B->>F: POST /api/v1/booths/{booth-id}/likes (credentials: include)
alt 쿠키 없음 (최초 요청)
F-->>B: Set-Cookie: deviceId=UUID; HttpOnly; Path=/; MaxAge=1년
else 쿠키 있음
Note over F: 기존 deviceId 사용
end
F->>S: deviceId를 request attribute로 전달
S->>R: SET like:rate:{ip}:{deviceId}:{boothId} 1 EX 1 NX
alt 1초 이내 재요청
R-->>S: nil (key 존재)
S-->>B: 429 Too Many Requests
else 정상 요청
R-->>S: OK
S->>R: ZINCRBY like:ranking 1 {boothId}
R-->>S: 증가된 score
S-->>B: 200 OK (누적 좋아요 수)
end
Note over B,S: 좋아요 수 조회
B->>S: GET /api/v1/booths/{booth-id}/likes
S->>R: ZSCORE like:ranking {boothId}
R-->>S: score 값
S-->>B: 200 OK (좋아요 수)
| 순서 | 시점 | 행동 | 비고 |
|---|---|---|---|
| 1 | 모든 API 요청 | credentials: 'include' 옵션 설정 | 브라우저가 쿠키 자동 전송 |
| 2 | 좋아요 버튼 클릭 | POST /api/v1/booths/{booth-id}/likes 호출 | 쿠키 없으면 서버 필터가 자동 발급 |
| 3 | 좋아요 수 표시 | GET /api/v1/booths/{booth-id}/likes 호출 | 쿠키 불필요 (공개 데이터) |
| 4 | 1초 내 재클릭 | 서버에서 429 Too Many Requests 응답 | 프론트에서 버튼 비활성화 처리 권장 |
해당 로직에서 프론트 분들은 낙관적 업데이트나 조회를 하지않고 내부적으로 카운트하는 상태 관리 등을 고민해보시면 될 것 같습니다.
해당 부분에 의견 부탁드립니다.