Programmer's Progress

도서 정보 제공 웹 서비스 - Gmail을 활용한 비밀번호 찾기 본문

Web Service/도서 정보 제공 웹 서비스

도서 정보 제공 웹 서비스 - Gmail을 활용한 비밀번호 찾기

Blanc et Noir 2022. 1. 9. 15:30

앞서 언급했듯이, 비밀번호는 절대로 복호화 가능한 형태로 암호화해서는 안 된다.

RSA든, AES든, DES든 어떠한 암호화 알고리즘을 사용하더라도 복호화가 가능하다면

이 비밀번호가 담긴 레코드를 탈취당했을 때 큰 문제가 발생할 것이다.

 

 

 

 

따라서 복호화가 불가능한, 단방향 해싱 알고리즘인 SHA512와 랜덤 한 값 SALT를 갖고 비밀번호를 암호화하였다.

그러나 복호화가 불가능하기 때문에 비밀번호 찾기를 구현하는 데 있어서 큰 문제가 있었다.

복호화가 불가능하기에, 기존의 비밀번호가 무엇인지 도저히 알 수가 없다는 것이다.

 

 

 

 

단순하게 JS의 alert( )를 이용해 기존 비밀번호를 출력하기에는 큰 문제가 있었다.

기존 비밀번호가 무엇인지 알지 못할 뿐만 아니라, 알고 있다고 하더라도

이 비밀번호를 DB에서 서버, 서버에서 클라이언트로 전송하는 과정에서 비밀번호 유출 가능성이 존재하기 때문이다.

따라서 기존 비밀번호를 그대로 사용자에게 보여주기보다는, 그냥 비밀번호 자체를 바꾸는 방식으로

사용자를 유도하는 것이 보안적인 측면에서도, 기술적인 측면에서도 좋았다.

 

 

 

 

 

그렇다면 문제는 변경된 비밀번호를 도대체 어떻게 사용자에게 전달할 것인가 하는 것이다.

사용자가 처음에 회원가입을 진행할 때 입력했던 메일 주소로 임시 비밀번호를 전달하는 방식으로 해결하는 것이

가장 좋은 방법이라고 생각했다. 그러려면 스프링 프레임워크에서 제공하는 MAIL과 관련된 기능을 활용하면

쉽게 해결되리라 생각했고, 바로 실행에 옮겼다.

 

 

 

 

 

 

 

package com.spring.LibraryService.controller;

import java.util.HashMap;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.spring.LibraryService.encrypt.SHA;
import com.spring.LibraryService.service.MailService;
import com.spring.LibraryService.vo.CustomerVO;

@Controller
@EnableAsync
public class MailController {
	@Autowired
	private MailService mailService;

	private void sendMail(String to, String subject, String text) throws Exception{
		mailService.sendMail(to,subject,text);
	}
	
	@RequestMapping(value="/mail/sendEmailAuthCode.do")
	@ResponseBody
	public HashMap<String,String> sendEmailAuthCode(HttpServletRequest request, HttpServletResponse response) {
		HashMap<String, String> map = new HashMap<String,String>();
		HttpSession session = request.getSession(true);
		CustomerVO customerVO = (CustomerVO) session.getAttribute("CUSTOMER");
		
		String EMAIL_AUTHCODE = SHA.DSHA512(SHA.getSalt(), SHA.getSalt()).substring(0, 16);
		String to = request.getParameter("CUSTOMER_EMAIL");
		String subject = "이메일 인증코드";
		String text = "이메일 인증코드는 "+EMAIL_AUTHCODE+" 입니다.";

		try {
			sendMail(to,subject,text);
			session.setAttribute("EMAIL_AUTHCODE", EMAIL_AUTHCODE);
			session.setAttribute("TEMP_EMAIL", to);
			map.put("FLAG", "TRUE");
			map.put("CONTENT", "해당 이메일로 인증번호를 전송했습니다.");
			return map;
		}catch(Exception e) {
			e.printStackTrace();
			map.put("FLAG", "FALSE");
			map.put("CONTENT", "인증번호 전송과정에서 오류가 발생했습니다.");
			return map;
		}
	}
}

메일과 관련된 서비스를 수행하는 메일 컨트롤러의 소스코드다.

 

 

이메일 인증에 사용할 16자리 코드는 랜덤 하게 생성된 SALT를 각각 SEED와 SALT로 활용하여

SHA를 두 번 적용한 후에 상위 16자리 글자를 사용하여 코드로 활용한다.

 

 

 

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
  <property name="host" value="smtp.gmail.com"></property>
  <property name="port" value="587"></property>
  <property name="username" value="지메일계정"></property>
  <property name="password" value="비밀번호"></property>
  <property name="javaMailProperties">
    <props>
       <prop key="mail.transport.protocol">smtp</prop>
       <prop key="mail.smtp.auth">true</prop>
       <prop key="mail.smtp.starttls.enable">true</prop>
       <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
       <prop key="mail.debug">true</prop>
    </props>
  </property>
</bean>
 
<!-- You can have some pre-configured messagess also which are ready to send -->
</beans>

이메일과 관련된 XML 소스코드

 

gmail에게 메일을 전송할 mailSender빈을 생성한다. 그리고 어노테이션을 이용해 주입한다.

포트번호는 587, 송신자는 나의 Gmail 계정을, 비밀번호는 구글에서 발급한 난수 비밀번호를 사용한다.

 

 

 

 

 

 

 

 

 

	@RequestMapping(value="/customer/join.do")
	@ResponseBody
	public HashMap<String,String> join(@ModelAttribute CustomerVO joinInfo, HttpServletRequest request, HttpServletResponse response) {
		HashMap<String,String> map = new HashMap<String,String>();
		HttpSession session = request.getSession(true);
		CustomerVO customerVO = (CustomerVO) session.getAttribute("CUSTOMER");
		if(customerVO!=null) {
			map.put("FLAG", "LOGON");
			map.put("CONTENT", "이미 로그인중입니다.");
			return map;
		}else if(session.getAttribute("EMAIL_AUTHCODE") == null || session.getAttribute("EMAIL_AUTHFLAG")==null || session.getAttribute("TEMP_EMAIL") == null){
			map.put("FLAG", "FALSE");
			map.put("CONTENT", "이메일 인증을 완료해야 합니다.");
			return map;
		}else if(!((String)session.getAttribute("TEMP_EMAIL")).equals(joinInfo.getCUSTOMER_EMAIL())){
			map.put("FLAG", "FALSE");
			map.put("CONTENT", "변경된 이메일에 대한 인증을 다시해야 합니다.");
			session.removeAttribute("EMAIL_AUTHCODE");
			return map;
		}else {
			String privateKey = (String) session.getAttribute("PRIVATEKEY");
			String TEMP = joinInfo.getCUSTOMER_PW();
			String CUSTOMER_PW = RSA2048.decrypt(TEMP, privateKey);
			
			String PASSWORD_HINT_ANSWER = RSA2048.decrypt(request.getParameter("PASSWORD_HINT_ANSWER"), privateKey);
			String PASSWORD_QUESTION_LIST_ID = request.getParameter("PASSWORD_QUESTION_LIST_ID");
			
			joinInfo.setCUSTOMER_PW(CUSTOMER_PW);
			return map = customerService.join(joinInfo,PASSWORD_QUESTION_LIST_ID,PASSWORD_HINT_ANSWER);
		}
	}

회원가입과 관련하여 소스코드 변경이 필요하다.

이메일 인증을 마친 이후에, 사용자가 회원가입 창에서 새로운 이메일 주소를 입력한 경우

새로운 이메일 주소에 대해서 유효성을 다시 인증해야 하기 때문이다.

 

 

 

 

 

비밀번호 찾기를 시연하는 모습

인증코드 메일이 도착하기까지 시간이 다소 걸리기는 하지만, 성공적으로 임시 비밀번호가 전송되고

임시 비밀번호로 로그인이 되는 것을 확인할 수 있다.

 

 

 

Comments