-
구글 reCAPTCHA v2, v3 사용해보기 🤖Project 2024. 8. 15. 11:52
자동화된 봇(🤖)과 악의적인 접근(😈)을 차단하는 데에 사용되는 CAPTCHA 기능을 사용해 보려고 한다. 무료로 사용할 수 있는 구글의 reCAPTCHA v2와 v3를 사용해보았다. v2는 사용자가 "로봇이 아닙니다" 라는 체크 박스에 체크를 하거나 특정 이미지를 선택해야 하는 등 사용자의 어떤 추가적인 행동이 필요하다. v3 방식은 사용자 경험을 개선한 것으로 사용자에게 추가적인 인증 작업을 요구하지 않는다는 것이 큰 장점이다. 이 방식은 사용자가 웹사이트에서 수행하는 활동을 분석하여 점수를 부여하고 이 점수를 바탕으로 봇인지 사람인지를 판단한다. reCAPTCHA v3는 0.0부터 1.0까지의 점수를 부여하며, 이 점수는 사용자가 봇일 가능성을 나타낸다. 높은 점수는 사람이 맞다는 것을, 낮은 점수는 봇일 가능성이 높다는 것을 의미한다. 사용자는 reCAPTCHA v3의 존재를 거의 인지하지 못하기 때문에, 사용자 경험을 방해하지 않으면서 보안을 강화할 수 있다! 🤖
https://www.google.com/recaptcha/admin/create
로그인 - Google 계정
이메일 또는 휴대전화
accounts.google.com
위 사이트에서 서비스에 필요한 사이트키와 비밀키를 획득할 수 있다.
v2 방식
간단한 글 작성 폼
- <head>에 reCAPTCHA 스크립트 추가
- 사용자가 체크박스에 체크하면 "g-recaptcha-response"라는 이름의 input 필드가 생성되고 토큰 값이 채워지게 된다. 이 토큰을 서버로 보내어 유효한지 체크해야 한다.
- 이쯤 되면 알 수 있을텐데, reCAPTCHA 방식은 프론트엔드와 벡엔드가 서로 협력하여 인증하는 방식이다.
- boardDto에서 토큰 값을 받을 수 있도록 폼 제출 전 적절하게 처리해 주었다.
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <!-- head 중략 --> <!-- v2 reCAPTCHA script --> <script src="https://www.google.com/recaptcha/api.js" async defer></script> </head> <body> <div class="container"> <!-- 중략 --> <!-- 글쓰기 모달 --> <div id="myModal" class="modal"> <div class="modal-content"> <span class="close">×</span> <h2>글 작성</h2> <form id="guestbookForm" action="/" method="post"> <label for="writer">작성자:</label> <input type="text" id="writer" name="writer" required><br><br> <label for="title">제목:</label> <input type="text" id="title" name="title" required><br><br> <label for="content">본문:</label><br> <textarea id="content" name="content" rows="4" cols="50" required></textarea><br><br> <!-- reCAPTCHA v2 체크박스 --> <div class="g-recaptcha" data-sitekey="사이트키 입력"></div> <input type="hidden" id="gRecaptchaResponse" name="gRecaptchaResponse"> <button type="submit" class="primary-btn">저장</button> </form> </div> </div> <script> // 폼 제출 클릭 시 document.getElementById('guestbookForm').addEventListener('submit', function(event) { var recaptchaResponse = document.querySelector('.g-recaptcha-response').value; if (!recaptchaResponse) { alert("Please complete the reCAPTCHA."); event.preventDefault(); return false; } document.getElementById('gRecaptchaResponse').value = recaptchaResponse; }); </script> <script src="/js/modal.js" defer></script> </body> </html>
controller
- 글쓰기 요청이 오면 우선 토큰 값만 꺼내어 사람인지 확인하고 봇이라도 판단되면 에러 페이지를 보여준다.
@Controller @RequiredArgsConstructor public class HomeController { private final BoardService boardService; private final RecaptchaService recaptchaService; // 중략 @PostMapping("/") public String write(@ModelAttribute BoardDto boardDto) { boolean isHuman = recaptchaService.verifyRecaptcha(boardDto.getGRecaptchaResponse()); if (!isHuman) { return "error"; } boardService.addBoard(boardDto); return "redirect:/"; } }
Board DTO
@Data public class BoardDto { private String writer; private String title; private String content; private String gRecaptchaResponse; }
Recaptha Service
- 전달 받은 토큰을 API 요청을 통해 유효한지 검증한다. 이 과정에서 생성된 비밀키가 사용된다.
@Service public class RecaptchaService { @Value("${google.recaptcha.secret-key}") private String secretKey; public boolean verifyRecaptcha(String token) { String url = "https://www.google.com/recaptcha/api/siteverify"; RestTemplate restTemplate = new RestTemplate(); // 헤더 설정 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // 바디 설정 MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("secret", secretKey); params.add("response", token); // 헤더와 바디를 포함한 HttpEntity 생성 HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers); // POST 요청 보내기 RecaptchaResponse response = restTemplate.postForObject(url, requestEntity, RecaptchaResponse.class); System.out.println("response = " + response); return response != null && response.isSuccess(); } }
Recaptcha Response
- score와 action은 v3 방식에서 사용하니 주석처리 해두었다.
@Data public class RecaptchaResponse { private boolean success; /* private float score; private String action; */ }
글 작성 모달
v3 방식
html 변경
- v3 방식에서는 사이트 키가 두 번 사용되는 것을 확인할 수 있다.
- 별도 체크 박스는 없다.
- 폼 제출 전 토큰을 설정해준다.
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <!-- 중략 --> <!-- v3 reCAPTCHA script --> <script src="https://www.google.com/recaptcha/api.js?render=v3 사이트 키" async defer></script> </head> <body> <div class="container"> <!-- 중략 --> <!-- 글 쓰기 모달 --> <div id="myModal" class="modal"> <div class="modal-content"> <span class="close">×</span> <h2>글 작성</h2> <form id="guestbookForm" action="/" method="post"> <label for="writer">작성자:</label> <input type="text" id="writer" name="writer" required><br><br> <label for="title">제목:</label> <input type="text" id="title" name="title" required><br><br> <label for="content">본문:</label><br> <textarea id="content" name="content" rows="4" cols="50" required></textarea><br><br> <!-- Hidden token --> <input type="hidden" id="gRecaptchaResponse" name="gRecaptchaResponse"> <button type="submit" class="primary-btn">저장</button> </form> </div> </div> <script> document.getElementById('guestbookForm').addEventListener('submit', function(event) { event.preventDefault(); // 폼 제출 방지 grecaptcha.ready(function() { grecaptcha.execute('v3 사이트 키', {action: 'submit'}).then(function(token) { document.getElementById('gRecaptchaResponse').value = token; document.getElementById('guestbookForm').submit(); // 토큰 설정 후 폼 제출 }); }); }); </script> <script src="/js/modal.js" defer></script> </body> </html>
controller, BoardDto
v2 방식과 동일
Recaptcha Response
- score : v3에서 판단된 점수 (0.0 ~ 1.0) *1에 가까울수록 사람이라고 판단*
- action : 사용자가 수행한 작업을 구분하기 위한 값. 구글은 해당 작업에 대해 더 정밀한 분석을 수행할 수 있으며, 서버 측에서도 action 필드를 확인하여 보안성을 강화할 수 있다.
@Data public class RecaptchaResponse { private boolean success; private float score; private String action; }
Recaptha Service
- isSuccess가 true이고, score가 0.5 이상이면 사람이라고 판단했다.
@Service public class RecaptchaService { @Value("${google.recaptcha.secret-key}") private String secretKey; private static final String RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"; public boolean verifyRecaptcha(String token) { RestTemplate restTemplate = new RestTemplate(); // 헤더 설정 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // 바디 설정 MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("secret", secretKey); params.add("response", token); // 헤더와 바디를 포함한 HttpEntity 생성 HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers); ResponseEntity<RecaptchaResponse> responseEntity = restTemplate.postForEntity( RECAPTCHA_VERIFY_URL, requestEntity, RecaptchaResponse.class ); RecaptchaResponse response = responseEntity.getBody(); System.out.println("response = " + response); return response != null && response.isSuccess() && response.getScore() >= 0.5; } }
오른쪽 하단을 확인하세요. 위 이미지에서 보이는 것처럼 v3 방식은 사용자가 어떤 인증 방식과 상호작용 하지 않는다. 우하단에 reCAPTCHA 아이콘만 확인이 가능하다. 이처럼 v3 방식은 사용자의 행동 패턴을 분석하여 점수 기반으로 위험도를 평가한다. 서버측에서는 이 점수에 따라 추가적인 검증을 처리할 수 있어 v2 방식보다 정교하게 처리할 수 있다.
어느 쪽이 더 나은 선택인지는 판단할 수 없겠지만 확실한 건 사용자의 명확한 검증이 필요하다면 v2가 적합할 수 있고, 사용자 경험을 중요시하면서도 보안을 유지하고자 한다면 v3가 더 나은 선택이 될 수 있다는 것이다.
'Project' 카테고리의 다른 글
프로젝트에 JWT 인증/인가 적용하기 (0) 2024.08.30 [팀 프로젝트] LMS 웹 사이트 만들기 (Learn Hub) (1) 2024.08.28 온라인 자바 컴파일러 만들기 (Judge0 API) (0) 2024.07.19 [팀 프로젝트] 애니메이션 커뮤니티 웹 사이트 만들기 (Who's Ducking) (0) 2024.07.07 [팀 프로젝트] 헬스장 키오스크 만들기 (JavaGym) (0) 2024.04.29