giftcon_dev/docs/security/recaptcha.md
2026-01-19 14:45:08 +09:00

5.4 KiB

Google reCAPTCHA v3 적용 메뉴얼 (Laravel 12)

이 문서는 PIN FOR YOU / gifticon-platform에서 Google reCAPTCHA v3(Score 기반)를 공통 컴포넌트 + 공통 JS + 서버 검증 Rule로 적용/운영하는 기준을 정리합니다.


0. 목표

  • reCAPTCHA v3는 사용자 화면에 체크박스가 나타나지 않습니다. (백그라운드 토큰+점수)
  • 폼/요청마다 action을 지정하고 서버에서 action 일치 + score 기준으로 판정합니다.
  • 디버깅은 storage/logs/google_recaptcha.log 전용 로그로 확인합니다.

1. Google 콘솔 설정 (1회)

  1. Google reCAPTCHA Admin Console에서 v3로 사이트 등록
  2. 도메인 등록 (예: four.syye.net, super.pinforyou.com 등)
  3. Site Key / Secret Key 발급

2. 환경변수/설정 (1회)

2.1 .env

RECAPTCHA_SITE_KEY=xxxx
RECAPTCHA_SECRET_KEY=yyyy
RECAPTCHA_MIN_SCORE=0.5
RECAPTCHA_LOG_LEVEL=info

2.2 config/services.php

'recaptcha' => [
    'site_key'  => env('RECAPTCHA_SITE_KEY'),
    'secret'    => env('RECAPTCHA_SECRET_KEY'),
    'min_score' => (float) env('RECAPTCHA_MIN_SCORE', 0.5),
],

3. 로깅 분리 (1회)

3.1 config/logging.php 채널 추가

channels 배열에 아래를 추가:

'google_recaptcha' => [
    'driver' => 'single',
    'path' => storage_path('logs/google_recaptcha.log'),
    'level' => env('RECAPTCHA_LOG_LEVEL', 'info'),
    'replace_placeholders' => true,
],

로그 파일 위치:

  • storage/logs/google_recaptcha.log

4. 공통 파일 (프로젝트 공통)

4.1 Layout에 스택 추가

resources/views/.../layout.blade.php (프로젝트 전체 레이아웃)에서 </body> 직전에 추가:

@stack('recaptcha')
@stack('scripts')

4.2 Blade 컴포넌트 (hidden input만)

resources/views/components/recaptcha-v3.blade.php

<input type="hidden" name="g-recaptcha-response" value="">

4.3 공통 JS 함수

public/assets/js/recaptcha-v3.js

(function () {
  function ensureReady() {
    return new Promise((resolve, reject) => {
      if (!window.grecaptcha) return reject(new Error('grecaptcha not loaded'));
      window.grecaptcha.ready(resolve);
    });
  }

  window.recaptchaV3Token = async function (action, formEl) {
    if (!window.__recaptchaSiteKey) throw new Error('__recaptchaSiteKey missing');
    await ensureReady();

    const token = await window.grecaptcha.execute(window.__recaptchaSiteKey, { action });

    if (formEl) {
      const input = formEl.querySelector('input[name="g-recaptcha-response"]');
      if (input) input.value = token;
    }
    return token;
  };
})();

5. 서버 검증 (필수)

5.1 Service

app/Services/RecaptchaV3.php

  • Google siteverify 호출
  • 정책 판단: success + action 일치 + score >= min_score

(구현은 app/Services/RecaptchaV3.php 파일 참고)

5.2 Rule

app/Rules/RecaptchaV3Rule.php

  • Validator에서 사용
  • 개발환경에서만 전용 로그 채널로 기록

6. 페이지 적용 방법 (폼마다)

6.1 Blade 폼 안에 hidden input 추가

<form id="someForm" onsubmit="return false;">
  @csrf
  <x-recaptcha-v3 />
  ...
</form>

6.2 해당 페이지에서만 로더 로드 (권장)

페이지 하단에 추가:

@push('recaptcha')
  <script>window.__recaptchaSiteKey = @json(config('services.recaptcha.site_key'));</script>
  <script src="https://www.google.com/recaptcha/api.js?render={{ config('services.recaptcha.site_key') }}"></script>
  <script src="{{ asset('assets/js/recaptcha-v3.js') }}"></script>
@endpush

폼 페이지가 많아지면 web.layouts.auth 같은 폼 전용 레이아웃에서 위 push를 공통화하면 더 편합니다.

6.3 fetch/AJAX 요청에 토큰 포함

const token = await window.recaptchaV3Token('auth_register_phone_check', form);

await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf, 'Accept': 'application/json' },
  body: JSON.stringify({
    ...payload,
    'g-recaptcha-response': token,
  }),
});

6.4 서버 Validator에 Rule 추가

use App\Rules\RecaptchaV3Rule;

Validator::make($request->all(), [
  'g-recaptcha-response' => ['required', new RecaptchaV3Rule('auth_register_phone_check')],
]);

주의: 프론트 action 문자열과 서버 Rule의 action완전히 동일해야 통과합니다.


7. Action 네이밍 규칙 (권장)

일관된 규칙으로 운영하면 로그 분석/정책 튜닝이 쉬워집니다.

  • auth_register_phone_check
  • auth_register_terms
  • auth_login
  • auth_findpw_send
  • auth_findpw_verify
  • cs_qna_create

8. 운영 튜닝

  • RECAPTCHA_MIN_SCORE는 초기 0.3~0.5 권장
  • score가 낮은 정상 사용자가 나오면 점수 기준을 낮추고, RateLimiter/IP 제한과 병행

9. 정상 동작 확인

9.1 브라우저 확인

Network 탭에서 요청 payload에 아래가 포함되어야 합니다.

  • phone
  • g-recaptcha-response

9.2 서버 로그 확인

storage/logs/google_recaptcha.log 예시:

  • success: true
  • score: 0.x
  • action 일치

10. 실패 테스트(개발용)

프론트에서 action을 일부러 틀리게 발급:

await window.recaptchaV3Token('auth_register_phone_check__FAIL_TEST', form);

서버에서 action mismatch로 실패해야 정상입니다.