JaeWon's Devlog
article thumbnail
반응형

실제 서비스하는 사이트들에서는 무작위한 로그인 시도 또는 회원가입을 막기 위해 reCAPTCHA와 같은 봇 방지 API를 사용합니다.

이 글에서는 google에서 제공하는 reCAPTCHA(리캡차)를 사용해 로그인을 진행하겠습니다.


현재 google reCAPTCHA는 v3 까지 나와있지만 이 글에서는 v2를 사용합니다.

0. 미리보기

- 로그인 페이지에 reCAPTCHA를 등록해보도록 하겠습니다.

- 로그인 구조는 아래 그림과 같이 진행될 것 입니다.

1. Google reCAPTCHA V2 생성

- 여기를 클릭하여 [Admin Console]로 이동하여 reCAPTCHA를 생성합니다.

- 정보를 입력하여 사용할 reCAPTCHA를 생성합니다.

- 도메인은 Local 환경에서 진행할 경우 localhost , 127.0.0.1 을 등록하고, 개인이 사용하고 있거나 등록할 사이트는
  사이트 주소를 입력하시면 됩니다.(ex: www.sitename.com)

- 생성된 Site Key(사이트키)와 Private Key(비밀키)를 확인합니다.

 

2. 프로젝트 생성

2-1. Dependency(의존성) 추가

- SpringBoot 프로젝트를 생성하고, reCAPTCHA 사용을 위한 Dependecny를 추가합니다.

- <Maven - Pom.xml>

<!-- Google reCAPTCHA -->
<dependency>
	<groupId>net.tanesha.recaptcha4j</groupId>
    <artifactId>recaptcha4j</artifactId>
    <version>0.0.7</version>
</dependency>

<!-- 구글 리캡챠 사용하기 위한 json -->
<dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1</version>
</dependency>

- <Gradle - build.gradle>

//	리캡차
implementation 'net.tanesha.recaptcha4j:recaptcha4j:0.0.7'

//	리캡차 사용을 위한 json
implementation 'javax.json:javax.json-api:1.1.2'
implementation 'org.glassfish:javax.json:1.1'

2-2. Config 파일 추가

- reCAPTCHA 인증을 검증하는 config class 파일을 추가합니다.

- http 통신을 통해서 구글API에 시크릿키와 인증성공한  토큰을 전달하여 검증을 요청합니다.

- 검증 결과는 json으로 전달되며, 검증 성공 여부를 파악하여 controller에 전달합니다.

import org.springframework.context.annotation.Configuration;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;

@Configuration
public class RecaptchaConfig {

    public static final String url = "https://www.google.com/recaptcha/api/siteverify";
    private final static String USER_AGENT = "Mozilla/5.0";

    private static String secret;

    public static void setSecretKey(String key){
        secret = key;
    }

    public static boolean verify(String gRecaptchaResponse) throws IOException {
        if (gRecaptchaResponse == null || "".equals(gRecaptchaResponse)) {
            return false;
        }

        try{
            URL obj = new URL(url);
            HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();

            // add reuqest header
            con.setRequestMethod("POST");
            con.setRequestProperty("User-Agent", USER_AGENT);
            con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");

            String postParams = "secret=" + secret + "&response="
                    + gRecaptchaResponse;

            // Send post request
            con.setDoOutput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.writeBytes(postParams);
            wr.flush();
            wr.close();

            int responseCode = con.getResponseCode();

            BufferedReader in = new BufferedReader(new InputStreamReader(
                    con.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            //parse JSON response and return 'success' value
            JsonReader jsonReader = Json.createReader(new StringReader(response.toString()));
            JsonObject jsonObject = jsonReader.readObject();
            jsonReader.close();

            return jsonObject.getBoolean("success");
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
    }
}

2-3. Controller 추가

- Controller.class 에는 클라이언트(view)에서 보내는 recaptcha 인증 값을 검증하기 위해 VeriVerifyRecaptcha.class에 보냅니다.

- 로그인 시도가 들어오면 리캡차 검증을 진행 후 통과하면 로그인 진행, 통과 실패 시 검증 실패를 전달합니다.

참고!!!
SecreyKey와 같은 경우에는 외부에 노출이 되면 안되기 때문에, 따로 properties 파일을 만들어 관리합니다.
(로컬개발시에는 하드코딩으로 사용하도 괜찮지만, git 같은 공유되는 곳에는 노출되면 안됩니다.)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/spring")
@PropertySource("classpath:dataSource.properties")
public class SpringRestController {

    @Value("${recaptcha.secretKey}")
    private String secretKey;

    @GetMapping("/recaptcha/login")
    public ResRecaptchaForm login(ReqRecaptchaForm form) {

        ResRecaptchaForm resp = new ResRecaptchaForm();

        //  [S]리캡차 검증
        try {
            RecaptchaConfig.setSecretKey(secretKey);
            Boolean verify = RecaptchaConfig.verify(form.getRecaptcha());

	// 검증 실패 시
            if(!verify){
                resp.setStatus(false);
                resp.setErrMsg("reCAPTCHA 검증 실패");

                return resp;
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        //  [E]리캡차 검증

	//검증 통과하면 로그인 진행 코드 작성
        ....

        return resp;
    }
}

2-4. View 추가

- 간단한 로그인 기능 있는 view 파일에 reCAPTCHA를 추가합니다.

<div>
	<input type = "text" id="loginId" name="memberId" placeholder="a@b.c" style="width:250px; height:30px;">
</div>
<div>
	<input type = "password" id="loginPw" name="memberPw" placeholder="비밀번호" style="width:250px; height:30px;">
</div>

<!-- reCAPTCHA 등록 -->
<div id="g-recaptcha"></div>
<br>

<div>
	<input type="button" class="disabled-btn" id="loginBtn" value="로그인" disabled>
</div>

- reCAPTCHA 기능을 사용하기 위해 script를 작성합니다.

- 로그인 시도는 reCAPTCHA 인증이 성공해야 시도 할 수 있도록 합니다.

<!-- Google reCAPTCHA js -->
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>

<script type="text/javascript">

  //	화면 시작 시 g-recaptcha 생성
  var onloadCallback = function() {
    grecaptcha.render('g-recaptcha', {
    'sitekey' : '6Ldto5saAAAAALjQt_YLT6L11O3NNFKcjgggPMb-',
    'callback' : verifyCallback,
    'expired-callback' : expiredCallback,
    });
  };

  //	인증 성공 시
  var verifyCallback = function(response) {
    $("#loginBtn").removeClass("disabled-btn");
    $("#loginBtn").attr("disabled", false);
  };

  //	인증 만료 시
  var expiredCallback = function(response) {
    $("#loginBtn").addClass("disabled-btn");
    $("#loginBtn").attr("disabled", true);
  }

  //	g-recaptcha 리셋
    var resetCallback = function() {
    grecaptcha.reset();
  }
</script>

- 로그인을 하기 위한 script도 작성합니다.

$("#loginBtn").on('click', function(){
   if($("#loginBtn").hasClass('disabled-btn')){
       alert("recaptcha 인증 후 진행이 가능합니다.");
   } else {
       var memberId = $("#loginId").val();
       var memberPw = $("#loginPw").val();
       var recaptcha = $("#g-recaptcha-response").val();

       $.ajax({
           type: "get",
           contentType: "application/json",
           url: "/spring/recaptcha/login",
           data: {
               memberId : memberId,
               memberPw : memberPw,
               recaptcha : recaptcha
           },
           dataType: "JSON",
           success: function(data){

               if(data.status == true){
                   로그인 성공시 코드 작성
               } else {
                   alert(data.errMsg);
               }
           },
           error: function(err){
               alert("에러" + err.toString());
           }
       });

   }
});

3. 프로젝트 실행

- 로그인 페이지로 진입하여, 로그인을 시도합니다.

reCAPTCHA 인증이 안되었을 경우 로그인 버튼 비활성화
reCAPTCHA 인증 시 로그인버튼 활성화

 

로그인 성공

- 구글 리캡차 어드민에서는 검증에 대한 차트도 확인 가능합니다.

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!