실제 서비스하는 사이트들에서는 무작위한 로그인 시도 또는 회원가입을 막기 위해 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. 프로젝트 실행
- 로그인 페이지로 진입하여, 로그인을 시도합니다.
로그인 성공
- 구글 리캡차 어드민에서는 검증에 대한 차트도 확인 가능합니다.
'Back-End > Spring' 카테고리의 다른 글
[QueryDSL]QueryDSL JPA 알아보기 Feat.Spring Data (0) | 2023.02.09 |
---|---|
[Spring] 스프링으로 OAuth2 로그인 구현하기 - 카카오 (0) | 2023.02.09 |
[Spring Security][회원가입 및 로그인 예제 추천] (0) | 2023.02.09 |
[Spring Boot]war로 배포하기 (Gradle + 내/외장 톰캣) (0) | 2023.02.09 |
[Spring Boot] Spring Security의 동작 (0) | 2023.02.09 |