[활용] Captcha - Google reCAPTCHA

Argonaut·2024년 12월 9일

captcha

목록 보기
2/2
post-thumbnail

captcha 구현방법


captcha를 구현하는 방법에는 크게 2가지 방법이 있습니다..

  1. 구글의 reCAPTCHA와 같은 API를 활용한 방법
  2. Captcha를 직접 구현하는 Custom Captcha 방법

이글에서는 외부 API를 활용하는 방법에 중점을 두고 설명하고자 합니다.


1. API를 이용한 Captcha 구현


특징

  • Google reCAPTCHA, hCaptcha 같은 외부 API를 활용해 Captcha를 간단히 통합가능.
  • 이미 검증된 알고리즘을 사용하므로, 구현이 쉽고 보안성이 우수함.
  • 다양한 형태의 Captcha를 지원: 이미지 선택, 텍스트 입력, Invisible Captcha 등.

장점

  • 구현 용이성
    Captcha를 직접 설계하지 않아도 되므로 시간과 리소스를 절약할 수 있음.
    제공되는 문서를 참고해 간단히 설정 가능.
  • 보안성
    Google이나 hCaptcha처럼 신뢰할 수 있는 대기업의 최신 기술을 기반으로 동작.
    봇의 진화를 반영한 지속적인 업데이트 제공.
  • 유지보수 부담 감소
    Captcha 시스템의 유지보수와 보안 강화는 API 제공 업체가 담당.

단점

  • 종속성
    외부 API 서비스에 의존적이므로, 해당 서비스가 중단되거나 문제가 생기면 시스템에 영향을 미침.
  • 프라이버시 우려
    사용자 데이터를 외부 서버(Google 등)로 전송하므로 개인정보 이슈가 발생할 수 있음.
  • 커스터마이징 제한
    제공되는 방식 외에는 원하는 기능이나 디자인으로 변경하기 어려움.

사용시기

  • 빠르고 간단하게 Captcha를 구현하고 싶은 경우.
  • 보안성이 중요하고, 유지보수에 신경을 덜 쓰고 싶은 경우.
  • 서비스의 규모가 작거나 Captcha 기능이 부가적인 요소일 때.

2. reCAPTCHA


Google에서 제공하는 스팸 및 악의적인 봇으로부터 웹사이트를 보호하는 보안 서비스

reCAPTCHA의 주요 버전

  1. reCAPTCHA v1 (초기 버전)

    • 왜곡된 문자나 숫자가 포함된 이미지를 사용자가 입력하여 검증하는 방식 사용.
    • 사용자가 입력해야 하는 번거로움이 있었고, OCR(광학 문자 인식) 기술 발전으로 효율성이 감소함. 그로 인해 현재는 더 이상 사용되지 않음.
  2. reCAPTCHA v2

    • 사용자가 체크박스를 클릭하거나 "I'm not a robot"을 클릭하면 사람임을 인증.
    • 체크박스 클릭과 같은 간단한 테스트를 진행
  3. reCAPTCHA v3

    • 특정 행동에 대해 점수를 매겨 사람이거나 봇인지 판단 (예시: 0.0~1.0의 점수)
    • 사용자에게 테스트를 강요하지 않음.
    • 임계값을 설정하여서 추가 검증이나 차단 여부를 결정

reCAPTCHA 구현과정

  1. reCAPTCHA 키 발급
    Google reCAPTCHA 웹사이트(https://www.google.com/recaptcha/)에 로그인 이후 키를 생성

    테스트를 위해 체크박스 형태로 진행

    사이트키와 비밀키는 저장
  1. 클라이언트 부분에 reCAPTCHA 추가

    • HTML를 사용하는 경우
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    ...
    <form action="/submit" method="POST">
      <div class="g-recaptcha" data-sitekey="사이트 키 입력"></div>
      <button type="submit">Submit</button>
    </form>
    • React를 사용하는 경우
    import ReCAPTCHA from "react-google-recaptcha";
    ...
    const [recaptchaToken, setRecaptchaToken] = useState<string | null>(null);
    
    const handleRecaptchaChange = (token: string | null) => {
        setRecaptchaToken(token); 				// 사용자가 인증되면 토큰 설정
        console.log("Recaptcha Token:", token);	// 테스트를 위한 로그
    };
    
    const handleSubmit = async (e: React.FormEvent) => {
    	  e.preventDefault();
          console.log("로그인 완료", { email, password });
    
          // reCAPTCHA 토큰이 없으면 경고 메시지 출력
          if (!recaptchaToken) {
              alert("캡챠 완료 후에 시도해주세요.");
              return;
          }
    
          // 서버로 토큰 전송
          const response = await fetch("검증 path", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ token: recaptchaToken }),
          });
    
    	  const result = await response.json();
          alert(result.message);
     };
    
    ...
    
    	<form onSubmit={handleSubmit}>
          <ReCAPTCHA
            sitekey="사이트 키" 
            onChange={handleRecaptchaChange}
          />
          <button type="submit">Submit</button>
      </form>

react 사용 중
-> npm install react-google-recaptcha로 패키지 설치
react-TS 사용 중
-> npm install --save-dev @types/react-google-recaptcha로 패키지 설치

`react-google-recaptcha`는 JS로 작성된 라이브러리이므로, TS는 기본적으로 해당 라이브러리의 구조와 타입을 알지 못해 에러가 발생
  1. 백엔드 부분에서 reCAPTCHA 검증 진행

    @Value("${recaptcha.secret}") // application.properties에 비밀 키 저장
      private String recaptchaSecret;
    @Value("${recaptcha.url}") // reCAPTCHA API URL 관리 (https://www.google.com/recaptcha/api/siteverify)
      private String recaptchaUrl;
    ...
    
    public ResponseEntity<APIResponse> recaptcha(@RequestBody Map<String, String> body) {
        String token = body.get("token");
    
        if (token == null || token.isEmpty()) {
            return ResponseEntity.badRequest().body(new APIResponse(HttpStatus.BAD_REQUEST.value(), "reCAPTCHA 토큰이 없습니다."));
        }
    
        RestTemplate restTemplate = new RestTemplate();
    
    	// MultiValueMap 자료 구조로 데이터를 저장
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("secret", recaptchaSecret);
        params.add("response", token);
    		
    	// HTTP Post 요청 진행
        ResponseEntity<Map> response = restTemplate.postForEntity(
    							url			// reCAPTCHA 검증 API의 URL
    							, params	// request 될 파라미터들
    							, Map.class	// 응답 데이터 수신 타입
    	  );
    
        Map responseBody = response.getBody();	// response 데이터
        boolean success = (Boolean) responseBody.get("success"); // response의 "success" 필드 값
    		
    	// 조건문으로 결과 처리
        if (success) {
            return ResponseEntity.ok(new APIResponse(HttpStatus.OK.value(), "검증 성공!"));
        } else {
            return ResponseEntity.badRequest().body(new APIResponse(HttpStatus.BAD_REQUEST.value(), "검증 실패!"));
        }
    }

    ❓ 일반적인 Map이 아닌 MultiValueMap을 사용한 이유는?

    Spring Boot의 RestTemplate을 사용하여 Google reCAPTCHA API와 통신할 때, HashMap이 아닌 MultiValueMap을 사용해야만 정상적으로 작동하는 이유는 HTTP 요청 데이터의 포맷(format)이 Google reCAPTCHA API가 요구하는 방식과 정확히 일치해야 하기 때문입니다.

    HashMap과 MultiValueMap의 차이점

  • HashMap
    1. 데이터를 단순히 Key-Value 구조로 저장
    2. 요청 데이터가 기본적으로 JSON 포맷으로 변환

    {
        "secret": "YOUR_SECRET_KEY",
        "response": "USER_TOKEN"
     }
  • MultiValueMap
    1. 데이터를 URL 인코딩된 Key-Value 쌍으로 변환
    2. 요청의 Content-Typeapplication/x-www-form-urlencoded로 설정

      secret=YOUR_SECRET_KEY&response=USER_TOKEN

    핵심 이유

    Google reCAPTCHA API는 요청 데이터를 application/x-www-form-urlencoded 형식으로 받기를 요구하기 때문에 MultiValueMap을 사용

    만약 HashMap을 데이터를 구성하였다면 application/x-www-form-urlencoded 형식에 맞게 변환하여 반환 요청 전송이 필요

  1. 결과확인
  • 체크를 진행 시 토큰을 전달 받음
  • Captcha 미진행 상태에서 로그인 시도
  • 확인
  • 실패

@참고문헌
https://developers.google.com/recaptcha/intro?hl=ko
https://velog.io/@coaudtn0276/ProjectReact%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-google-reCAPTCHA

profile
성장하는 개발자가 되기 위한 기록 일지

0개의 댓글