Canvas라는 elements를 이용해 그림을 그릴 수 있는 기능을 사용할 수 있다. 

해당 캔버스엔 선, 원과 같은 그림을 그리는 기능을 사용할 수 있는데. 

Script 에서 제공하는 이벤트 기능과 합쳐 그림판을 만들 수 있는 것이다.

 

어떻게 동작하는건지 알아보자 

원리는 간단하다!

canvas위 마우스 클릭 이벤트를 감지해서 현재 마우스가 위치한 영역에 좌표를 구하고,

그 좌표에 작은 원을 그려준다. 

마우스가 이동할때 감지되는 mousemove 이벤트는 계속해서 현재 마우스의 위치를 

가져오고 그 위치에 계속해서 원을 그려주는 것이다. 

이렇게 되면 마치 우리가 연필로 그림을 그리듯한 모습을 볼수있다. 

마우스가 움직일때마다 그림을 그려주면 마우스가 움직일때마다 그림을 그리기 떄문에 

아래 코드를 보면 painting 이라는 flag를 추가했다. 

마우스가 클릭되면 이 flag를 true로 변경해 그림을 그리게 시키고.. 

마우스 클릭이 off되면 flag를 false로 변경해서 더이상 그림을 그리지 않도록 처리되는 것이다. 

 

touch 이벤트도 감지하도록 아래 코드에 적어뒀기 때문에 

아이패드, 노트북 펜슬 등으로 터치해도 기능이 잘 동작한다! 

 

아래 샘플코드를 그대로 복사하면

샘플을 확인할 수 있다~ 

 

<canvas id="canvas" ></canvas>
<script src="https://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>
	
	// 캔버스 객체를 가져온다.
	var canvas = document.getElementById("canvas"),
	ctx = canvas.getContext("2d"),
	painting = false, // 그림을 그릴 상황인지 체크하는  flag
	lastX = 0, // 마지막 x 좌표 
	lastY = 0, // 마지막 y 좌표 
	lineThickness = 1; // 펜 굵기 
	
	
	// canvas 사이즈설정	
	canvas.width  = window.innerWidth;
	canvas.height = window.innerHeight;
	
	
	// 터치감지 이벤트  
	 document.body.addEventListener("touchstart", function (e) {
		if (e.target == canvas) {e.preventDefault(); }
		var touch = e.touches[0];
    	var mouseEvent = new MouseEvent("mousedown", {
			clientX: touch.clientX,
			clientY: touch.clientY
		});
		canvas.dispatchEvent(mouseEvent);
				
	}, false);
	
	
	document.body.addEventListener("touchend", function (e) {
		if (e.target == canvas) {
			e.preventDefault();
		}
		
		var touch = e.touches[0];
    	var mouseEvent = new MouseEvent("mouseup", {
			clientX: touch.clientX,
			clientY: touch.clientY
		});
		canvas.dispatchEvent(mouseEvent);
		
	}, false);
	document.body.addEventListener("touchmove", function (e) {
		if (e.target == canvas) {
			e.preventDefault();
		}
		
		var touch = e.touches[0];
		var mouseEvent = new MouseEvent("mousemove", {
			clientX: touch.clientX,
			clientY: touch.clientY
		});
		canvas.dispatchEvent(mouseEvent);
		
	}, false);
		
    
	// 마우스 이벤트 	
	canvas.onmousedown = function(e) {
	    painting = true;
	    ctx.fillStyle = "#000";
	    lastX = e.pageX - this.offsetLeft;
	    lastY = e.pageY - this.offsetTop;
	};

	canvas.onmouseup = function(e){
	    painting = false; // 마우스로 그림그리기가 끝나면 플래그를 변경해준다. 
	}

	
	// 그림을 그려주는 function
	canvas.onmousemove = function(e) {
		
	    if (painting) {
	        mouseX = e.pageX - this.offsetLeft;
	        mouseY = e.pageY - this.offsetTop;

	        // find all points between        
	        var x1 = mouseX,
	            x2 = lastX,
	            y1 = mouseY,
	            y2 = lastY;


	        var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
	        if (steep){
	            var x = x1;
	            x1 = y1;
	            y1 = x;

	            var y = y2;
	            y2 = x2;
	            x2 = y;
	        }
	        if (x1 > x2) {
	            var x = x1;
	            x1 = x2;
	            x2 = x;

	            var y = y1;
	            y1 = y2;
	            y2 = y;
	        }

	        var dx = x2 - x1,
	            dy = Math.abs(y2 - y1),
	            error = 0,
	            de = dy / dx,
	            yStep = -1,
	            y = y1;
	        
	        if (y1 < y2) {
	            yStep = 1;
	        }
	       
	        lineThickness = 3 - Math.sqrt((x2 - x1) *(x2-x1) + (y2 - y1) * (y2-y1))/10;
	        if(lineThickness < 1){
	            lineThickness = 1;   
	        }

	        for (var x = x1; x < x2; x++) {
	            if (steep) {
	                ctx.fillRect(y, x, lineThickness , lineThickness );
	            } else {
	                ctx.fillRect(x, y, lineThickness , lineThickness );
	            }
	            
	            error += de;
	            if (error >= 0.5) {
	                y += yStep;
	                error -= 1.0;
	            }
	        }

	        lastX = mouseX;
	        lastY = mouseY;

	    }
	}
    
	
	// 캔버스에 이미지를 그려준다. 
	// 나의 경우 이미지 위에 그림을 그려야해서 사용함. 
	// 생략가능 
    setBackground();
    function setBackground() {
	    var image = new Image();
	    image.onload = function() {
		    ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
	    }
	    image.src = "/resources/images/suit.png";

    }
    

</script>

 

 

결과 

 

 

* 추가적으로  Hammer.js 를 사용하면  모바일 환경에서 발생되는 더 많은 터치기능(확대, swipe, tab)을 처리할 수 있다. 

 

 

OpenCV 작업환경 구성! 

1. 라이브러리 다운 

아래 사이트에 접속해서 라이브러리 다운로드가 필요하다. 

https://opencv.org/releases/

 

Releases - OpenCV

Become a Member Stay up to date on OpenCV and Computer Vision news Join our Newsletter  

opencv.org

 

사이트 접속하면 메인화면에 나오는 부분에서 Window를 클릭해준다. 

페이지가 이동되면서 파일은 자동으로 다운로드 된다, 

 

다운로드 받은 파일의 압축을 풀어준다. 

 

압축이 풀린 파일을 들어가보면 java를 위한 jar 파일이 있다. 

 

 

이클립스를 열어서 라이브러리를 추가해준다. 

 

추가한 라이브러리의 location을 설정해주는데 

아까 다운받은 파일의 디렉토리를 보면  아래와 같은 .dll 파일을 찾을 수 있다. 이 경로로 설정해준다. 

 

스탬프 순서대로 진행해주면 된다.  

 

 

테스트를 진행할 Class 를 만든다. 

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;

public class OpenCVTest{

	public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        System.out.println(Core.NATIVE_LIBRARY_NAME);
        Mat mat = Mat.eye(5, 5, CvType.CV_8UC(1));
        System.out.println(mat.dump());

    }
}

 

결과코드로 아래와 같이 나오면 준비 완료! 

'OCR' 카테고리의 다른 글

[JAVA] OpenCV + Tesseract 사용법  (0) 2022.04.08
[Maven] Tesseract 사용법  (0) 2022.04.08
OCR 알아보기 시작!!  (0) 2022.04.07

1. 먼저 Tesseract 라이브러리를 불러온다

maven 사이트 접속해서 오늘날짜 기준 가장 최신버전으로 선택했다. 

https://mvnrepository.com/artifact/net.sourceforge.tess4j/tess4j/5.2.0

 

2. pom.xml에 dependency 추가. 

<dependency>
	<groupId>net.sourceforge.tess4j</groupId>
	<artifactId>tess4j</artifactId>
	<version>4.5.2</version>
</dependency>

 

3. 테스트를 위한 Class 생성 ! 

import java.io.File;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;


public class OcrTest {
		
	 public static void main(String[] args) {
		 try {
		    // 읽어볼 이미지를 가져온다. 
		    File image = new File("경로상생략/res.jpg");

                    Tesseract tesseract = new Tesseract();
                    tesseract.setDatapath("경로상생략/tessdata"); //** 학습된데이터가 있는 폴더를 지정해준다. 
                    tesseract.setLanguage("eng"); // 언어설정 
                    tesseract.setPageSegMode(1); // 페이지 모드 설정
                    tesseract.setOcrEngineMode(1); 
                    // tesseract.setHocr(true); // html로 그려주는 flag
                    String result = tesseract.doOCR(image);
                    System.out.println(result);

		 
		 } catch (TesseractException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		 }
		 
		 
	 }

}

  

 tesseract.setDatapath("경로상생략/tessdata");  이 라인은 tesseract가 이미지를 읽었을때 

비교할 학습된 데이터 파일의 경로를 가르킨다. 

아래 링크에 접속해보면 이미 학습된 데이터를 제공하고 있으므로 내가 하고자 하는 언어를 다운받아서 넣어두면 된다. 

https://github.com/tesseract-ocr/tessdata

 

GitHub - tesseract-ocr/tessdata: Trained models with support for legacy and LSTM OCR engine

Trained models with support for legacy and LSTM OCR engine - GitHub - tesseract-ocr/tessdata: Trained models with support for legacy and LSTM OCR engine

github.com

 

접속해보면 아래와 같은 파일들이 나오는데 앞 글자가 언어를 가르키고 있다. 

 

나는 영문과 중문이 필요하므로 아래 이미지와 같이 구성하였다. 

 

 

테스트로 사용한 이미지 

 

결과 ) 

 

 

결론)

위에 올린 이미지 말고도 여러개의 이미지를 테스트 해보았으나 

연습용 정도의 퀄리티만 나올 뿐, 업무에 적용해보긴 어려울거 같다는 판단이 든다. 

 

어떻게 하면 인식률을 올릴 수 있을까? 

 OpenCV 라이브러리를 이용해 이미지 흑백으로 바꾸고 블러등등.. 을 이용해

이미지를 더 깔끔하게 만들어 준다면  인식하는데 효과가 있다고한다.

OpenCV 적용을 해보도록한다. 

 

* 손글씨는 50% 정도만 인식되는걸로 보임. 

 

'OCR' 카테고리의 다른 글

[JAVA] OpenCV + Tesseract 사용법  (0) 2022.04.08
Java + OpenCV 사용법  (0) 2022.04.08
OCR 알아보기 시작!!  (0) 2022.04.07

Apple로 로그인 기능을 써보았다. 

타 서비스는 oAuth2 로 연결하였으나 애플의 경우 직접 처리해본다!!! 

 

순서는 이러하다. 

1. front  로그인 버튼을 클릭하면 apple 로그인 화면이 뜬다. 

2. 사용자가 로그인을 완료하면 개발자 센터에 등록해두었던 redirect url로 애플에서 데이터를 쏴준다. 

3. Controller에서 애플에서 넘겨준 아래와 같은 데이터를 받는다. 

{ 
state: 1111111
code: casq31253s211w35321sd15w3132e116w534s23
user: {"name":{"firstName":"Genie","lastName":"Park"},"email":"myemail@naver.com"}
}

4. 사용자가 최초로 로그인을 하였거나, 본인의 기기에서 애플로 로그인 기능을 지웠다가 다시 로그인하는 경우엔 user 데이터가 들어있다. 

5.  code 유효성 검사를 진행한다. 유효한 code라면 애플에서 아래와 같은 데이터를 넘겨준다. 

{
"access_token": "qwe4q5w3e48qwe46q5we4q6w5e4qw.qw5e4q6we54qw",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "we4q5w3e48qwe46q5we4q6w5e4qw.qw5e4q6we54qwasd.asdasdasd",
  "id_token": "asdasdasdasdasdasdasd3qwe4qw2.e1qwe654qw6e54q6w8e4q9w8ea.eqw5e6q4w8dq48q6wd4"
}

 

6.  넘겨준 id_token를 디코드하면 아래와 같은 정보를 얻을 수 있다. 

{
  "at_hash": "asd21a3sd8asd3554as3d",
  "aud": "com.genie.service",
  "sub": "00asd00.14qw0eqwe55.qwwwe410000",
  "nonce_supported": true,
  "email_verified": "true",
  "auth_time": 1231231231,
  "iss": "https://appleid.apple.com",
  "exp": 123123123123,
  "iat": 13123123123,
  "email": "myemail@naver.com"
  }

 

이렇게 받은 데이터를 가지고 내가 원하는 기능을  검증히고 구현하면 된다!! 

 

 

 

코드로 봅시다!!! 

 

1. 애플 개발자 센터 가입 후 아래의 키들을 발급받는다. 

public static final String TEAM_ID = "팀아이디";
public static final String REDIRECT_URL = "애플에 등록한 리다이렉트 url"
public static final String CLIENT_ID = "Service ID 등록시 작성했던 Identifier";
public static final String KEY_ID = "MYKEY "; // .p8 파일을 다운받았다면 파일명에 적혀있다.
public static final String AUTH_URL = "https://appleid.apple.com";
public static final String KEY_PATH = "static/AuthKey_MYKEY.p8";

 

2. Front에 버튼을 생성한다. 

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>Sign in with Apple </title>
  
  <meta name="appleid-signin-client-id" content="서비스아이디 발급시 사용한 Identifier">
  <meta name="appleid-signin-scope" content="name email"> <!-- 이름과 이메일을 받는다-->
  <meta name="appleid-signin-redirect-uri" content="개발자센터에 등록한 리다이렉트 url">
  <meta name="appleid-signin-state" content="임의의 문자열">
  <meta name="appleid-signin-nonce" content="임의의 문자열">
  <meta name="appleid-signin-use-popup" content="true"> <!-- or false defaults to false -->
  
</head>
<body>

   <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
   <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
 
</html>

 

3. Controller 에서 redirect 부분을 작업해본다. 

import java.net.URISyntaxException;
import java.text.ParseException;
import java.time.LocalDateTime;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;

import com.me.genie.util.AppleUtils;
import com.me.genie.util.HttpClientUtils;


@PostMapping("/auth/apple")
public CommonResponse<UserAuthForUserDto> authByApple( 
HttpServletRequest request
, @RequestParam(value = "code", required= false) String code
, @RequestParam(value = "user", required= false) String user
, HttpServletResponse response) throws ParseException, JsonMappingException, JsonProcessingException, URISyntaxException {


/*********************************************************************
* 애플 로그인 취소시 null 리턴  
* ******************************************************************/
if(code == null) return null;


/*********************************************************************
* code 유효성 검증 
* 유효한 토큰이라면 id_token 이 포함된 데이터를 리턴해준다.
* ******************************************************************/
String apiResponse = AppleUtils.validateAuthorizationGrantCode(code);
if(apiResponse == null) { 
logger.info("CODE 유효성 검사 실패");
return null;
}


/*********************************************************************
* id-token 유효성 검증 
* ******************************************************************/
JSONObject tokenResponse = (JSONObject) JSONValue.parse(apiResponse);
if(!AppleUtils.verifyIdentityToken((String) tokenResponse.get("id_token"))) {
logger.info("ID-TOKEN 유효성 검사 실패");
return null;
}


/*********************************************************************
* id_token 토큰을 파싱하여 데이터를 가져온다.
* ******************************************************************/
JSONObject appleInfo = AppleUtils.decodeFromIdToken((String) tokenResponse.get("id_token"));
logger.debug(tokenResponse.toJSONString());
logger.debug(appleInfo.toJSONString());


/*********************************************************************
* user 정보는 최초 1회만 제공 받을 수 있다.
* 단, 고객이 본인의 아이디 관리에서 로그인을 해지한 경우엔 다시 받을 수 있다.
* ******************************************************************/
String username = "", email = (String) appleInfo.get("email");
User target = null;



if(user != null) { // 가입한적이 없거나, 애플로 로그인 기능을 삭제 후 다시 접근하는 경우.
    
    // 사용자 정보를 추출한다. 
	JSONObject u = (JSONObject) JSONValue.parse(user);
	username = (String) u.get("lastName") + (String) u.get("firstName"); 
	email = (String) u.get("email");
    
    // TODO: 여기서부터 자유롭게 로직을 진행하면 된다. 
    // 예) username과 email을 이용해 가입이력 조회 
    // 없다면 가입, 있다면 로그인 처리
    
} else { 
	// 기존에 가입한적이 있는 경우 여기로 들어온다. 
	if(email == null) {
      return null;
	}
 	
    // 기존에 가입한적이 있는 경우 refresh_token  유효성 검사를 진행한다. 
    if(AppleUtils.validateAnExistingRefreshToken((String) tokenResponse.get("refresh_token")) == null) {
      logger.info("refresh_token 유효성 검사 실패");
      return null;
    }
			
	if(email == null) {
		return null;
	}
    
    /*********************************************************************
    * 로그인 화면에서 사용자가 이메일을 공개로 로그인한 경우 이메일 값이 들어온다.
    * 진짜 이메일이 들어오고 test111@naver.com
    * 나의 이메일 숨기기를 선택했다면 email":"7ct4qm6gdn@privaterelay.appleid.com 와 같은 이메일이 들어온다.
    * ******************************************************************/
  	email = (String) appleInfo.get("email");
  
    // TODO: 여기서부터 자유롭게 로직을 진행하면 된다. 
    // 예) email or refesh_token을 통해 사용자 가입이력을 조회한뒤 처리한다. 
	// (refesh_token은 기한이 없음.)
}

 

 

4. Controller에서 사용한 Utils

package com.unli.cogba.util;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.core.io.ClassPathResource;

import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class AppleUtils {
	public static final String TEAM_ID = "team id";
    public static final String REDIRECT_URL = "애플에 등록한 리다이렉트 url"
    public static final String CLIENT_ID = "Service ID 등록시 작성했던 Identifier";
    public static final String KEY_ID = "MYKEY "; // .p8 파일을 다운받았다면 파일명에 적혀있다.
    public static final String AUTH_URL = "https://appleid.apple.com";
    public static final String KEY_PATH = "static/AuthKey_MYKEY.p8";
	
	
	/**
	 * createClientSecret 생성
	 * @return
	 */
    public static String createClientSecret() {
    	PrivateKey pKey = readPrivateKey(KEY_PATH);
        String token = Jwts.builder()
                .setHeaderParam(JwsHeader.KEY_ID, KEY_ID)
                .setIssuer(TEAM_ID)
                .setAudience("https://appleid.apple.com")
                .setSubject(CLIENT_ID)
                .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 5)))
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .signWith(pKey, SignatureAlgorithm.ES256)
                .compact();
        return token;
    }   
    
    /**
     * 파일에서 private key 획득
     * @return Private Key
     */
    private static PrivateKey readPrivateKey(String keyPath) {
    	try { 
	    	ClassPathResource resource = new ClassPathResource(keyPath);
	        String privateKey = new String(Files.readAllBytes(Paths.get(resource.getURI())));
	        Reader pemReader = new StringReader(privateKey);
	        PEMParser pemParser = new PEMParser(pemReader);
	        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
	        PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
	        return converter.getPrivateKey(object);
    	} catch (Exception e) {
			e.printStackTrace();
		}
    	return null;
    }
    
    /**
     * id_token 검증
     * @param id_token
     * @return
     */
    public static boolean verifyIdentityToken(String id_token) {
    	try {
    		SignedJWT signedJWT = SignedJWT.parse(id_token);
    		JWTClaimsSet payload = signedJWT.getJWTClaimsSet();

			// EXP
			Date currentTime = new Date(System.currentTimeMillis());
		    if (!currentTime.before(payload.getExpirationTime())) {
		    	return false;
		    }

		    
		    if (!"none에 입력했던값".equals(payload.getClaim("nonce")) || !AUTH_URL.equals(payload.getIssuer()) || !CLIENT_ID.equals(payload.getAudience().get(0))) {
		    	return false;
		    }

	    // RSA
	    if (verifyPublicKey(signedJWT)) {
	    	return true;
	    }
	  } catch (Exception e) {
	  	e.printStackTrace();
	  }

	  return false;
	}
    
    
    /**
     * 공개키 검증
     * @param signedJWT
     * @return
     */
    private static boolean verifyPublicKey(SignedJWT signedJWT) {
        try {
            String publicKeys = HttpClientUtils.doGet("https://appleid.apple.com/auth/keys");
            ObjectMapper objectMapper = new ObjectMapper();
            Keys keys = objectMapper.readValue(publicKeys, Keys.class);
            for (Key key : keys.getKeys()) {
                RSAKey rsaKey = (RSAKey) JWK.parse(objectMapper.writeValueAsString(key));
                RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
                JWSVerifier verifier = new RSASSAVerifier(publicKey);

                if (signedJWT.verify(verifier)) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    
    
    /**
     * refresh_token 유효성 검증
     * @param client_secret
     * @param refresh_token
     * @return
     */
    public static String validateAnExistingRefreshToken(String refresh_token) {
    	String reqUrl = AUTH_URL + "/auth/token";
        Map<String, String> tokenRequest = new HashMap<>();
        tokenRequest.put("client_id", CLIENT_ID);
        tokenRequest.put("client_secret", createClientSecret());
        tokenRequest.put("grant_type", "refresh_token");
        tokenRequest.put("refresh_token", refresh_token);

        return HttpClientUtils.doPost(reqUrl, tokenRequest);
    }
    
    /**
     * code 유효성 검증
     * @param code
     * @return
     */
    public static String validateAuthorizationGrantCode(String code) {
    	try {
	        String reqUrl = AUTH_URL + "/auth/token";
		    
	        Map<String, String> tokenRequest = new HashMap<>();
		    tokenRequest.put("client_id", CLIENT_ID);
		    tokenRequest.put("client_secret", createClientSecret());
		    tokenRequest.put("code", code);
		    tokenRequest.put("grant_type", "authorization_code");
		    String apiResponse = HttpClientUtils.doPost(reqUrl, tokenRequest);
		    
	        return apiResponse;
    	} catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    
    
    /**
     * id_token decode
     * @param id_token
     * @return
     */
    public static JSONObject decodeFromIdToken(String id_token) {
        try {
            SignedJWT signedJWT = SignedJWT.parse(id_token);
            JWTClaimsSet getPayload = signedJWT.getJWTClaimsSet();
    	    String appleInfo = getPayload.toJSONObject().toJSONString();
    	    JSONObject payload = (JSONObject) JSONValue.parse(appleInfo);

            if (payload != null) {
                return payload;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}

 

5. 통신을 위한 util

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HttpClientUtils {

    private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
    private static ObjectMapper objectMapper = new ObjectMapper();

    public static String doGet(String url) {
        String result = null;
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        Integer statusCode = null;
        String reasonPhrase = null;

        try {
            httpclient = HttpClients.createDefault();
            HttpGet get = new HttpGet(url);
            response = httpclient.execute(get);
            statusCode = response.getStatusLine().getStatusCode();
            reasonPhrase = response.getStatusLine().getReasonPhrase();
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, "UTF-8");
            EntityUtils.consume(entity);

            if (statusCode != 200) {
                logger.error(String.format("[doGet]http get url(%s) failed. status code:%s. reason:%s. result:%s", url, statusCode, reasonPhrase, result));
            }
        } catch (Throwable t) {
            logger.error(String.format("[doGet]http get url(%s) failed. status code:%s. reason:%s.", url, statusCode, reasonPhrase), t);
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                if (httpclient != null) {
                    httpclient.close();
                }
            } catch (IOException e) {
                logger.error(String.format("[doGet]release http get resource failed. url(%s). reason:%s.", url, e.getMessage()));
            }
        }

        return result;
    }

    public static String doPost(String url, Map<String, String> param) {
        String result = null;
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        Integer statusCode = null;
        String reasonPhrase = null;
        try {
            httpclient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
            List<NameValuePair> nvps = new ArrayList<>();
            Set<Entry<String, String>> entrySet = param.entrySet();
            for (Entry<String, String> entry : entrySet) {
                String fieldName = entry.getKey();
                String fieldValue = entry.getValue();
                nvps.add(new BasicNameValuePair(fieldName, fieldValue));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps);
            httpPost.setEntity(formEntity);
            response = httpclient.execute(httpPost);
            statusCode = response.getStatusLine().getStatusCode();
            reasonPhrase = response.getStatusLine().getReasonPhrase();
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, "UTF-8");

            if (statusCode != 200) {
                logger.error(String.format("[doPost]post url(%s) failed. status code:%s. reason:%s. param:%s. result:%s", url, statusCode, reasonPhrase, objectMapper.writeValueAsString(param), result));
            }
            EntityUtils.consume(entity);
        } catch (Throwable t) {
            try {
                logger.error(String.format("[doPost]post url(%s) failed. status code:%s. reason:%s. param:%s.", url, statusCode, reasonPhrase, objectMapper.writeValueAsString(param)), t);
            } catch (JsonProcessingException e) {
            }
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                if (httpclient != null) {
                    httpclient.close();
                }
            } catch (IOException e) {
                try {
                    logger.error(String.format("[doPost]release http post resource failed. url(%s). reason:%s, param:%s.", url, e.getMessage(), objectMapper.writeValueAsString(param)));
                } catch (JsonProcessingException ex) {
                }
            }
        }
        return result;
    }

}

 

공개키 검증을 위한 utils

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Key {
    private String kty;
    private String kid;
    private String use;
    private String alg;
    private String n;
    private String e;
}
import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Keys {
    private List<Key> keys;

}

 

 

끝!!!!!!!!!!!!!!!!!!!! 

 

 

1. 캡차를 사용하기 위해 아래 링크로 접속해 사이트 등록을 진행한다. 

https://www.google.com/recaptcha/about/

 

 

2.  아래 이미지 순서로 수행한다!  캡챠는 여러 버전이 있으니 원하는바로 선택하면 된다. 

나는 체크박스 형태로 진행하기로 함! (체크박스를 원하면 그대로 따라하면 됨!)

 

 

도메인 부분에서 참고! 

로컬에서 테스트를 하려면 아래와 같이 localhost 등록도 해주어야한다. 

 

 

저장하면 비밀키와 사이트 키를 준다. 잘 저장해두자! 

 

 

이제 html를 작성해보자 

우선 캡차를 사용하기 위해 header에  스크립트를 추가해준다. 

<script src="https://www.google.com/recaptcha/api.js" async defer></script>

 

원하는 위치에 체크박스 캡챠 추가. 

@captcha.getSite() ---> 이부분에 앞서 발급받은 사이트 키를 입력해준다!! 

<div id="recaptcha" class="g-recaptcha" th:attr="data-sitekey=${@captcha.getSite()}" data-callback="recaptchaCallback"></div>

 

화면을 새로고침하면 캡차가 표시되는걸 볼 수 있다. 

 

 

이제 사용자가 캡챠 수행을 했는지 여부를 스크립트 단에서 확인해본다. 

캡차를 수행하게되면 grecaptcha.getResponse() 에 값이 들어오게 되는데 

이 값을 기준으로 수행여부를 가볍게 체크해본다. 

 

if(grecaptcha.getResponse() == "") {
	alert("캡챠를 수행하세요!");
}

 

본 비즈니스 로직을 타기 전 서버단에서도 한번 더 체크해준다. 

 grecaptcha.getResponse() 값을 다시 구글에 검증하는 로직이다. 

나의 경우 Interceptor을 만들어 캡챠수행 여부를 검증하고 정상적이지 않은 경우 튕겨내는 방식으로 작업을 진행했다. 

public static boolean verifyRecaptcha(String recaptcha) {
    	final String SECRET_KEY = "구글 비밀키"; // 비밀키 호출
    	final String RE_URL = "https://www.google.com/recaptcha/api/siteverify";// 인증할 URL
    	
		try {
			URL obj = new URL(RE_URL);
			HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
			con.setRequestMethod("POST");
			
			String postParams = "secret=" + SECRET_KEY + "&response=" + recaptcha;
			con.setDoOutput(true);
			
			DataOutputStream wr = new DataOutputStream(con.getOutputStream());
			wr.writeBytes(postParams);
			wr.flush();
			wr.close();

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

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

			JsonReader jsonReader = Json.createReader(new StringReader(response.toString()));
			JsonObject jsonObject = jsonReader.readObject();
			jsonReader.close();
			
			return jsonObject.getBoolean("success"); //최종 Return 값 : true or false
			
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

 

 

 

+ Recent posts