본문 바로가기
Stack Overflow/스프링

[스프링] 구글 리캡챠(reCAPTCHA) v3 개발 적용 방법(JSP)

by Lich King 2021. 12. 23.

구글 리캡챠(reCAPTCHA)는 크게 v2와 v3 으로 구분된다.
v2는 우리가 흔히 아는 랜덤 이미지를 선택하거나, 퍼즐을 조립하는 식으로 인간 여부를 판별하게 된다.
v3은 시스템에 내장하여 점수(Score)를 통해서 사람 여부를 판별한다.

대개 이정도로 아는 사람들이 많은데.... 리캡챠(reCAPTCHA) v3 는 v2 보다 더 좋은 장점이 한가지가 더 존재한다.
임의의 랜덤 토큰을 사용하여 검증을 진행하기 때문에 점수(Score) 변수로 추가 개발을 하지 않더라도, 프론트 단위에서 임의의 값을 서버로 넘겨 매크로나 반복작업을 하지 못하게 할 수 있다.
즉, 시간지연이 오래걸리는 작업 부분에서 디도스와 같은 반복작업공격을 막는데에 있어 탁월한 도움이 된다.
내용이 잘 이해가 되지 않는다면 하단 내용에서 확인하시면 된다.

1. 요약
1.1 구글에서 리캡챠(reCAPTCHA) 계정등록
1.2 개발 진행

2. 리캡챠(reCAPTCHA) 가입 진행
https://www.google.com/recaptcha/admin/create
상기 링크로 들어가서 리캡챠(reCAPTCHA) 가입을 진행한다. 리캡챠(reCAPTCHA)를 개발하려는 개발자에게는 별도의 설명이 필요하지 않다고 생각된다.
공개키와 비밀키만 확인하면 정상적으로 진행이 완료된 것이다.

3. JSP 프론트단 뷰(VIEW) 개발

<script src="https://www.google.com/recaptcha/api.js?render=공개키"></script> grecaptcha.ready(function() { grecaptcha.execute('공개키', {action: 'submit'}).then(function(token) { $.ajax({ type : "POST", url : "reCAPTCHA_URL", data : {"token" : token}, success : function(data) { // 성공 처리 }, error : function(request, status, msg) { // 실패 처리 } }); }); });

회원 가입에서 진행한 공개키값만 입력하면 JSP단의 개발은 완성

4. JAVA 서버단 개발
4.1 클래스 DTO

import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @JsonPropertyOrder({ "success", "challenge_ts", "hostname", "error-codes" }) public class RecaptchaDTOEntity { @JsonProperty("success") private String success; @JsonProperty("challenge_ts") private String challenge_ts; @JsonProperty("hostname") private String hostname; @JsonProperty("score") private String score; @JsonProperty("action") private String action; @JsonProperty("error-codes") private ErrorCode[] errorCodes; @JsonIgnore public boolean hasClientError() { ErrorCode[] errors = getErrorCodes(); if(errors == null) { return false; } for(ErrorCode error : errors) { switch(error) { case InvalidResponse: case MissingResponse: return true; } } return false; } static enum ErrorCode { MissingSecret, InvalidSecret, MissingResponse, InvalidResponse; private static Map<String, ErrorCode> errorsMap = new HashMap<>(4); static { errorsMap.put("missing-input-secret", MissingSecret); errorsMap.put("invalid-input-secret", InvalidSecret); errorsMap.put("missing-input-response", MissingResponse); errorsMap.put("invalid-input-response", InvalidResponse); } @JsonCreator public static ErrorCode forValue(String value) { return errorsMap.get(value.toLowerCase()); } } public String getSuccess() { return success; } public void setSuccess(String success) { this.success = success; } public String getChallenge_ts() { return challenge_ts; } public void setChallenge_ts(String challenge_ts) { this.challenge_ts = challenge_ts; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public String getScore() { return score; } public void setScore(String score) { this.score = score; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public ErrorCode[] getErrorCodes() { return errorCodes; } public void setErrorCodes(ErrorCode[] errorCodes) { this.errorCodes = errorCodes; } @Override public String toString() { return "RecaptchaDTO [success=" + success + ", challenge_ts=" + challenge_ts + ", hostname=" + hostname + ", score=" + score + ", action=" + action + "]"; } }

이정도만 하면 부족할 것이 없다.

4.2 서버 데이터 처리

@RequestMapping(value="reCAPTCHA_URL", method=RequestMethod.POST) public ModelAndView reCAPTCHA_URL(HttpServletRequest request , HttpServletResponse response, Model model ) throws Exception { ModelAndView mav= new ModelAndView(); try { String token = request.getParameter("token"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>(); map.add("secret", SECRET_KEY); map.add("response", token); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers); RecaptchaDTOEntity entity = restTemplate.postForObject(CommonCode.SITE_VERIFY_URL, request, RecaptchaDTOEntity.class); if(entity != null) { String score = entity.getScore(); // 점수 String success = entity.getSuccess(); // true, false } return mav; }


SECRET_KEY 항목에는 회원가입할 때 얻은 비밀키를 넣어준다.
postForObject 메소드를 통해 REST 통신이 완료된 이후 NULL값이 아니거나 Exception 오류가 나지 않는 다면 token으로서의 연할은 다한 것이다.
이 말은 즉, 초반에 얘기했던 jsp 프론트단에서의 데이터 변조를 token 값의 변조를 통해 막을 수 있다는 의미이다.
추가적으로 사람임을 검증하고 싶으면 Score로 점수를 확인한다.
구글에서는 90점인 경우 사람이라고 판단한다고 가이드를 제시하고 있다.

이렇게 개발해도 잘 안될 것이다.
왜냐하면 SSL 도 적용해야 하기 때문이다.
SSL 적용 확인은 하단 링크에서 읽어보고 추가 개발하도록 한다.

SSL 적용을 위한 링크 진행 : https://baekyle.tistory.com/5

댓글