# SMS 발송/인증 적용 메뉴얼 (Laravel) 이 문서는 **SMS 인증번호 발송/검증**을 프로젝트에서 일관되게 적용하기 위한 기준을 정리합니다. > 목적: 자동 입력/도배 방지 + 인증 품질 향상 + 운영 중 추적 가능(로그/레이트리밋) --- ## 0. 권장 설계 개요 - **발송(send)** / **검증(verify)** API를 분리 - 휴대폰 번호는 서버에서 표준화(normalize) 후 저장/비교 - 과도한 시도 방지: **RateLimiter** + (선택) IP/기기 세션 제한 - 코드 저장: (권장) 캐시/DB 모두 가능 - 캐시(예: Redis) 사용 시 만료(TTL) 관리가 쉬움 - DB 저장은 감사/추적(감사로그)에 유리 --- ## 1. 엔드포인트 권장 스펙 ### 1.1 인증번호 발송 - `POST /auth/phone/send-code` - request - `phone` (string) - `g-recaptcha-response` (string, v3 권장) - response - `{ ok: true }` 또는 `{ ok: false, message }` ### 1.2 인증번호 검증 - `POST /auth/phone/verify-code` - request - `phone` - `code` - `g-recaptcha-response` - response - `{ ok: true, redirect: ... }` 또는 `{ ok: false, message }` --- ## 2. 번호 표준화 (필수) 서버에서 입력값을 한국 휴대폰 숫자만으로 정규화: - 입력: `010-1234-5678`, `010 1234 5678`, `+82 10-1234-5678` - 저장/비교: `01012345678` 권장 함수 예시: ```php private function normalizeKoreanPhone(string $raw): ?string { $digits = preg_replace('/\D+/', '', $raw); if (!$digits) return null; // +82 처리(예: 8210xxxxxxxx) if (str_starts_with($digits, '82')) { $digits = '0' . substr($digits, 2); } // 010/011/... 휴대폰 기준(프로젝트 정책에 맞게 조정) if (!preg_match('/^01[016789]\d{7,8}$/', $digits)) { return null; } // 최종 10~11자리 if (strlen($digits) < 10 || strlen($digits) > 11) return null; return $digits; } ``` --- ## 3. RateLimiter 정책 (권장) ### 3.1 휴대폰 기준 발송 제한 예: 10분에 5회 - key: `sms:send:{phone}` - limit: 5 - decay: 600초 ### 3.2 IP 기준 제한(선택) - key: `sms:sendip:{ip}` ### 3.3 코드 검증 시도 제한 예: 10분에 10회 - key: `sms:verify:{phone}` --- ## 4. 인증코드 생성/저장/만료 ### 4.1 코드 생성 - 6자리 숫자 - 예: `random_int(100000, 999999)` ### 4.2 저장(권장: Cache/Redis) - key: `sms:code:{phone}` - value: 해시 저장 권장 - ttl: 180초~300초 예시: ```php $code = (string) random_int(100000, 999999); $hash = hash_hmac('sha256', $code, config('app.key')); Cache::put("sms:code:{$phone}", [ 'hash' => $hash, 'created_at' => now()->toDateTimeString(), ], now()->addMinutes(3)); ``` 검증 시: ```php $stored = Cache::get("sms:code:{$phone}"); if (!$stored) { /* 만료 */ } $hash = hash_hmac('sha256', $inputCode, config('app.key')); if (!hash_equals($stored['hash'], $hash)) { /* 실패 */ } Cache::forget("sms:code:{$phone}"); // 성공 시 1회용 ``` --- ## 5. SMS Provider 연동 위치 - 실제 발송은 `app/Services/SmsService.php` 같은 서비스 레이어로 분리 권장 - 컨트롤러는: 1) validate 2) rate limit 3) code 생성/저장 4) SmsService 호출 5) 응답 반환 --- ## 6. 로깅/감사(권장) - 민감정보는 최소화 - 전화번호는 마스킹 로그 권장: `010****5678` - 인증코드 원문 로그 금지 권장 전용 로그 파일: - `storage/logs/sms.log` (logging channel을 별도 구성하면 recaptcha와 동일한 방식으로 분리 가능) --- ## 7. 보안 체크리스트 - [ ] reCAPTCHA(v3) 적용 (send/verify 모두) - [ ] RateLimiter 적용 - [ ] 인증코드 TTL 적용 - [ ] 성공 시 1회용 처리(Cache forget) - [ ] 실패 횟수 제한(verify) - [ ] 동일 번호 반복 요청 시 UX 메시지(남은 시간 안내 등) - [ ] 운영 로그 분리(필요 시) --- ## 8. API 응답 메시지 정책(권장) 공격자에게 힌트가 되지 않게, 실패 메시지는 통일: - 발송 실패: `처리에 실패했습니다. 잠시 후 다시 시도해 주세요.` - 검증 실패: `인증번호가 올바르지 않습니다.` (횟수 초과/만료는 별도 가능) --- ## 9. 운영 튜닝 포인트 - 발송 제한(10분 5회)은 초기 보수적으로 시작 → 운영 로그 보고 조정 - 통신사/지연으로 인증 도착이 늦어질 수 있으니 TTL 3~5분 권장