HD

[구글 OTP] java 본문

JAVA

[구글 OTP] java

hunecenter 2021. 9. 14. 11:11
반응형

참고 : https://lasdri.tistory.com/793

 

OTP 앱

안드로이드: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ko

iOS: https://itunes.apple.com/kr/app/google-authenticator/id388497605?mt=8

 

프로젝트 중 구글 OTP 인증을 통한 로그인 구현을 요청받아 구글링 하던중 API문서나 따로 정보를 찾은게 없어서...

해당 참고 글 소스랑 같지만 GoogleOTP로직을 util로 쓰고 로그인 serviceImpl에서 otp인증을 하도록 수정해서 구현

 

-절차 ※OTP 고유키값은 DB에 저장 

1. id,password 맞는지 체크 

2. googleOTP 고유키값 DB에 있는지 없는지 체크  

3. 없을시 Google OTP 앱으로 바코드 등록 유도 or 있을때 otp 6자리 인증

4. 로그인 완료

 

-Git 소스 주소

https://github.com/hunecenter94/googleOtp.git

 

GitHub - hunecenter94/googleOtp: googleOtp

googleOtp. Contribute to hunecenter94/googleOtp development by creating an account on GitHub.

github.com

 

-Java

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Random;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base32;

public class GoogleOTP {
	/**
	 * OTPKEY 생성 및 QR코드 생성	
	 * @param userName
	 * @param hostName
	 * @return
	 */
	public static HashMap<String, String> generate(String userName, String hostName) {
		HashMap<String, String> map = new HashMap<String, String>();
		byte[] buffer = new byte[5 + 5 * 5];
		new Random().nextBytes(buffer);
		Base32 codec = new Base32();
		byte[] secretKey = Arrays.copyOf(buffer, 10);
		byte[] bEncodedKey = codec.encode(secretKey);

		String encodedKey = new String(bEncodedKey);
		String url = getQRBarcodeURL(userName, hostName, encodedKey);
		// Google OTP 앱에 userName@hostName 으로 저장됨 (양식은 상관이 없음)
		// key를 입력하거나 생성된 QR코드를 바코드 스캔하여 등록

		map.put("encodedKey", encodedKey);
		map.put("url", url);
		
		return map;
	}

	/**
	 * OTP번호 check
	 * @param userCode Google OTP 앱에 표시되는 6자리 숫자
	 * @param otpkey Google OTP PK
	 * @return
	 */
	public static boolean checkCode(String userCode, String otpkey) {
		long otpnum = Integer.parseInt(userCode); // Google OTP 앱에 표시되는 6자리 숫자
		long wave = new Date().getTime() / 30000; // Google OTP의 주기는 30초
		boolean result = false;
		try {
			Base32 codec = new Base32();
			byte[] decodedKey = codec.decode(otpkey);
			int window = 3;
			for (int i = -window; i <= window; ++i) {
				long hash = verify_code(decodedKey, wave + i);
				if (hash == otpnum) result = true;
			}
		} catch (InvalidKeyException | NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return result;
	}
	
	/**
	 * OTP_KEY 검증
	 * @param key
	 * @param t
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
		byte[] data = new byte[8];
		long value = t;
		for (int i = 8; i-- > 0; value >>>= 8) {
			data[i] = (byte) value;
		}

		SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
		Mac mac = Mac.getInstance("HmacSHA1");
		mac.init(signKey);
		byte[] hash = mac.doFinal(data);

		int offset = hash[20 - 1] & 0xF;

		// We're using a long because Java hasn't got unsigned int.
		long truncatedHash = 0;
		for (int i = 0; i < 4; ++i) {
			truncatedHash <<= 8;
			// We are dealing with signed bytes:
			// we just keep the first byte.
			truncatedHash |= (hash[offset + i] & 0xFF);
		}

		truncatedHash &= 0x7FFFFFFF;
		truncatedHash %= 1000000;

		return (int) truncatedHash;
	}

	/**
	 * QR코드 주소 생성
	 * @param user
	 * @param host
	 * @param secret
	 * @return
	 */
	public static String getQRBarcodeURL(String user, String host, String secret) {
		// QR코드 주소 생성
		String format2 = "http://chart.apis.google.com/chart?cht=qr&chs=200x200&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s&chld=H|0";
		return String.format(format2, user, host, secret);
	}
}

- Controller

/**
 * otp process
 * @param userInfo
 * @param googleCode
 * @param model
 * @return
 */
@RequestMapping("/otpProcess")
	public String otpProcess(
		String userId,
		String inputUserPwd,
		ModelMap model
	){
			
	String result = loginService.ajaxProcess(null, userId, inputUserPwd);
	JsonObject obj = new JsonObject();
	obj.addProperty("result", result);
	model.addAttribute( "obj", obj ); 
	return "obj";
}

-ajax 처리

function loginSubmit(form) {
	var userId = $("#userId").val();
	var userPwd = $("#userPwd").val();
	var chk = true;
	var message = "";
	
	if(userId==null || userId=='' || userId=='undefined'){
		chk = false;
		$("#userIdErr").remove();
		message = "<span class='_err' id='userIdErr'>아이디를 입력해주세요.</span>";
		$("#userIdChk").append(message);
	}else{
		$("#userIdErr").remove();
	}
	
	if(userPwd==null || userPwd=='' || userPwd=='undefined'){
		chk = false;
		$("#userPwdErr").remove();
		message = "<span class='_err' id='userPwdErr'>패스워드를 입력해수세요.</span>";
		$("#userPwdChk").append(message);
	}else{
		$("#userPwdErr").remove();
	}
	
	if(chk){
		$.ajax({
			type:"post",
			url:kurl( '/otpProcess' ),
			data:{"userId":userId, "inputUserPwd":userPwd},
			async:false,
			cache:false,
			success:function(r) {
				if(r.result=="OTP_KEY_HAVE") {
					var url2= kurl("/login");
					var html = "<div class=\"_blockInner\">";
						html += "	<form method='post' action='"+url2+"'>";
						html += "	<a href=\"#close\" onclick=\"$.unblockUI();\" class=\"_blockClose\">close</a>";
						html += "	<input type='hidden' id='userId' name='userId' value='"+userId+"'>";
						html += "	<input type='hidden' id='userPwd' name='userPwd' value='"+userPwd+"''>";
						html += "	<div class='_write'>";
						html += "		<div class='_form _both'>";
						html += "			<label class='_label' for='googleCode'>OTP번호</label>";
						html += "			<div class='_insert'>";
						html += "				<input type='text' name='googleCode' id='googleCode'>";
						html += "		</div>";
						html += "	</div>";
						html += "	</div>";
						html += "	<div class='_areaButton'>";
						html += "		<div class='_center'>";
						html += "			<span class='_button _large _active'><input type='submit' value='확인'></span>";
						html += "		</div>";
						html += "	</div>";
						html += "	</form>";
						html += "</div>";
					$.blockUI({
						message:html
						
					});
				}else if(r.result=="NOT_MATCHED_PWD"){
					var html = 	"<div class=\"_blockInner\">";
						html += "	<a href=\"#close\" onclick=\"$.unblockUI();\" class=\"_blockClose\">close</a>";
						html += "아이디와 패스워드가 일치하지 않습니다.";
						html +="</div>";
				
					$.blockUI({
						message:html
						
					});
				}else{
					var qrUrl = r.result;				
					var html = 	"<div class=\"_blockInner\">";
						html += "	<a href=\"#close\" onclick=\"$.unblockUI();\" class=\"_blockClose\">close</a>";
						html += "	<iframe name=\"blockUI\" src=\""+qrUrl+"\"></iframe>";
						html += "QR 코드를 GOOGLE OTP앱으로 바코드 등록해주세요.";
						html +="</div>";
				
					$.blockUI({
						message:html
						
					});
				}
			}
		});
	}
}

 

 

 

 

 

반응형
Comments