스프링부트+jsp로 배달사이트 만들기-38 이메일보내기(아이디 찾기,비번찾기)

2021. 12. 29. 17:58스프링부트

이메일을 보낼 준비를 합니다

구글 계정관리 클릭

 

 

보안 > 2단계인증 후, 앱 비밀번호 생성

 

 

 

pom.xml에 라이브러리를 추가합니다

<!-- SMTP -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

 

 

application.properties에 설정을 추가합니다

# SMTP
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=지메일
spring.mail.password=앱 키
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.auth=true

 

 

SimpleMailMessage에 메세지를 담아서 JavaMailSender로 메일을 보낼 수 있습니다

 

simpleMailMessage

  • setTo() 받는사람 이메일 주소
  • setSubject() 이메일 제목
  • setText() 내용

 

 


아이디 찾기

 

userInfo 폴더를 만들고 jsp, css, js를 추가합니다

 

findId.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/view/include/link.jsp" %>
<link rel="stylesheet" href="/css/layout/nav.css">
<link rel="stylesheet" href="/css/userInfo/find.css">
<%@ include file="/WEB-INF/view/include/header.jsp" %>

	
	<main class="find_id_page">
		<div class="find_info">
			<h3>가입하신 이메일을 입력해주세요</h3>
			<input type="email" name="email" class="email">
			<button class="find_btn">찾기</button>
			<div class="find_password"><a href="/find/password">비밀번호 찾기</a></div>
		</div>
	</main>	

	
<%@ include file="/WEB-INF/view/include/nav.jsp" %>	
<%@ include file="/WEB-INF/view/include/footer.jsp" %>	

	<script src="/js/userInfo/findId.js" ></script>

</body>
</html>

 

find.css

@charset "utf-8";

h3 {
	font-weight: normal;
}

main {
	width: 100%;
	text-align: center;
	min-height: calc(100vh - 341px);
}

main input {
	font-size: 1.5rem;
}

main button {
	background: #30DAD9;
	color: #fff;
	padding: 1px 5px;
	border: none;
	height: 30px;
	border-radius: 5px;
}

.find_info {
	width: 350px;
	max-width: 90%;
	margin: 0 auto;
	margin-top: 50px;
}

.find_id_page .find_info input, .find_password_page .find_info input {
	border-radius: 5px;
	border: 1px solid #666;
	width: 75%;
	min-width: 150px;
	height: 30px;
	padding: 0 5px;
}

.find_password {
	text-align: left;
	margin: 10px;
}

.find_password a {
	text-decoration: underline;
}

.send_email {
	margin-top: 50px;
}

.send_email h2 {
	display: inline;
}

.find {
	text-align: left;
}

.find>div {
	margin-bottom: 20px;
}

.find input[type=radio]:checked ~ .auth {
	display: block;
}

.auth {
	margin: 10px;
	display: none;
}

.auth input {
	border: 1px solid #ddd;
	padding: 0 5px;
	height: 30px;
	margin-bottom: 10px;
}

.password_modify_page .find_info {
	width: 450px;
}

.password_modify_page h3 {
	margin-bottom: 20px;
}

.password_modify_page .box {
	text-align: left;
	margin-bottom: 5px;
}

.password_modify_page .box span {
	display: inline-block;
	width: 120px;
}

.password_modify_page .box input {
	border: 1px solid #ddd;
	padding: 0 5px;
	height: 30px;
	max-width: 250px;
	width: 60%;
}

.password_modify_page .modify_btn {
	margin-top: 20px;
}

.password_modify_page .password_check_msg {
	text-align: center;
}

@media ( max-width : 767px) {
	body>div {
		min-height: calc(100vh - 276px);
		width: 90%;
		margin: 0 auto;
	}
}

@media ( max-width : 480px) {
	.password_modify_page .box span {
		width: 90px;
	}
}

 

 

findId.js

$(document).ready(function() {
	$(".find_btn").click(function() {
		const email = $(".email").val();
		if (!emailCheck(email)) {
			swal("이메일을 정확히 입력해주세요");
			return;
		}

		$.ajax({
			url: "/find/id/sendUsernames",
			type: "POST",
			data: { email: email }
		})
		.done(function() {
			const html =
				`<div class="send_email">
					<div>
						<h3>${email }</h3>
						<span>으로 아이디를 전송했습니다</span><br>
						<div>가입한 적이 없는 이메일 주소나 올바르지 않은 이메일 주소를 입력하신 경우에는 메일을 받을 수 없습니다.</div>
						<button class="back_btn">돌아가기</button>
					</div>
				</div>`;

			$("main").html(html);

		})
		.fail(function() {
			alert("에러가 발생했습니다");
		})
	})


	$(document).on("click", ".back_btn", function() {
		location.href = "/login";
	})
})

 

 

 

 

입력한 이메일로 가입된 아이디를 보낼 메서드를 추가합니다

// 메일로 아이디 보내기
@PostMapping("/find/id/sendUsernames")
public ResponseEntity<Object> sendEmail(String email){
    List<String> usernames =userService.findId(email);

    if(usernames.size() != 0) {
        mailService.sendUsernames(email, usernames);
    }
    
    return new ResponseEntity<Object>(HttpStatus.OK);
}

findId()로 아이디 리스트를 가져오고 리스트사이즈가 0이 아닐때 메일을 보냅니다

 

UserService와  UserDAO에 추가합니다

 

UserServiceImp

@Override
public List<String> findId(String email) {
    return userDAO.findId(email);
}

 

UserDAOImp

@Override
public List<String> findId(String email) {
    return sql.selectList("user.findId", email);
}

 

UserMapper

<select id="findId" resultType="String">
    SELECT	USERNAME
    FROM	BM_USER
    WHERE	EMAIL = #{email }
</select>

 

Service패키지에 MailService와 MailServiceImp을 추가합니다

 

MailService

public interface MailService {

	void sendUsernames(String email, List<String> usernames);

}

 

MailServiceImp

@Service
public class MailServiceImp implements MailService{

	@Autowired
	private JavaMailSender mailSender;
	
	@Override
	public void sendUsernames(String email, List<String> usernames) {
		SimpleMailMessage simpleMailMessage = new  SimpleMailMessage();
		simpleMailMessage.setTo(email);
		simpleMailMessage.setSubject("아이디 찾기");
		
		StringBuffer sb = new StringBuffer();
		sb.append("가입하신 아이디는");
		sb.append(System.lineSeparator());
		
		for(int i=0;i<usernames.size()-1;i++) {
			sb.append(usernames.get(i));
			sb.append(System.lineSeparator());
		}
		sb.append(usernames.get(usernames.size()-1)).append("입니다");
		
		simpleMailMessage.setText(sb.toString());
		
		
		new Thread(new Runnable() {
			public void run() {
				mailSender.send(simpleMailMessage);
			}
		}).start();
	}

	
}

 

mailSender.send(simpleMailMessage); 부분이 메일을 전송하는 코드인데 메일전송에 걸리는시간이 3~5초정도 걸리기 때문에 스레드로 감싸주었습니다

 

UserInfoController에서 Service를 주입받습니다

@Autowired
private UserService userService;

@Autowired
private MailService mailService;

 

 

 

 

 

 


비밀번호 찾기

 

비밀번호 찾기 페이지를 준비합니다

findPassword.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/view/include/link.jsp" %>
<link rel="stylesheet" href="/css/layout/nav.css">
<link rel="stylesheet" href="/css/userInfo/find.css">
<%@ include file="/WEB-INF/view/include/header.jsp" %>
	
	<main class="find_password_page">
		<div class="find_info">
			<h3>가입하신 아이디를 입력해주세요</h3>
			<input type="text" name="username" class="username">
			<button type="button" class="next_page">다음</button>
		</div>
	</main>
	
	
	
<%@ include file="/WEB-INF/view/include/nav.jsp" %>	
<%@ include file="/WEB-INF/view/include/footer.jsp" %>	

	<script src="/js/userInfo/findPassword.js"></script>


<script>

</script>
</body>
</html>

 

findPassword.js

$(document).ready(function(){
	
function usernameCheck() {
	let submit = false;
	const username = $("input[name=username]").val().replaceAll(" ", "");
	if(!username) {
		return false;
	}
	
	$.ajax({
		url: "/overlapCheck",
		type: "GET",
		async: false,
		data: {
			value : username,
			valueType : "username"
		}
	})
	.done(function(result){
		if(result == 1) {
			submit = true;
		} 
	})
	.fail(function(){
		alert("에러");
	})
	return submit;
}




$(".next_page").click(function(){
	if(!usernameCheck()) {
		swal("아이디를 정확히 입력해주세요");
		return;
	}
	const data = {
		username : $(".username").val(),	
	}
	
	$.ajax({
		url: "/find/password/auth",
		type: "POST",
		data: data
	})
	.then(function(result){
		location.href= "/find/password/auth?username=" + result;
	})
	.fail(function(){
		alert('에러');
	})
})



	

	
})

 

컨트롤러에 비밀번호찾기 페이지 메서드를 추가합니다

// 비밀번호 찾기 페이지
@GetMapping("/find/password")
public String findPassword() {
    return "userInfo/findPassword";
}

 

 

아이디를 입력 후 다음버튼을 눌렀을 때 가입되어있는 아이디라면 인증번호 보내기 페이지로 이동합니다

// 5분동안 유저확인 세션생성 (인증완료 X)
@PostMapping("/find/password/auth")
public ResponseEntity<Object> authenticateUser(String username, HttpSession session) {
    Map<String, Object> authStatus = new HashMap<>();
    authStatus.put("username", username);
    authStatus.put("status", false);
    
    session.setMaxInactiveInterval(300);
    session.setAttribute("authStatus", authStatus);
    return new ResponseEntity<Object>(username, HttpStatus.OK);
}

주소창에 직접 주소를 입력해 인증번호찾기 페이지로 이동할 수 없게 세션에 아이디를 저장했습니다

 

인증번호 보내기 페이지를 추가합니다

auth.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/view/include/link.jsp" %>
<link rel="stylesheet" href="/css/layout/nav.css">
<link rel="stylesheet" href="/css/userInfo/find.css">
<%@ include file="/WEB-INF/view/include/header.jsp" %>
	
	<main>
		<div class="find_info">
			
			<div class="find">
				<div id="find_by_email">
					<input type="radio" id="email" value="email" name="find">
					<label for="email">가입한 이메일로 찾기</label>
					
					<div class="auth">
						<input type="email" class="email" placeholder="이메일을 입력해주세요" maxlength="50">
						<button type="button" class="auth_num_send_eemail">인증번호받기</button>
						<input type="text" class="auth_num" name="authNum" readonly maxlength="6"  placeholder="인증번호6자리입력">
						<span class="timer"></span>
					</div>
				</div>
				
				
				<div id="find_by_phone">
					<input type="radio" id="phone" value="phone" name="find">
					<label for="phone">전화번호로 찾기</label>
					<div class="auth">
						<input type="text" class="phone" maxlength="11" placeholder="전화번호를 입력해주세요">
						<button type="button" class="auth_num_send_phone" >인증번호받기</button>
						<input type="text" class="auth_num" name="authNum" readonly maxlength="6"  placeholder="인증번호6자리입력">
						<span class="timer"></span>
					</div>
				</div>
				
			</div>
				
			<button class="move_modify">다음</button>
		</div>
	</main>
	
	
	
<%@ include file="/WEB-INF/view/include/nav.jsp" %>	
<%@ include file="/WEB-INF/view/include/footer.jsp" %>	

	<script src="/js/userInfo/auth.js"></script>


<script>

</script>
</body>
</html>

 

 

auth.js

$(document).ready(function(){
	
const URLSearch = new URLSearchParams(location.search);
const username = URLSearch.get("username"); 

		
	
		
// 인증번호 발송했는지 여부
const authNum = (function(){
	let send = false;
	const isSend = function(set){
		if(!set) {
			return send;
		} else {
			send = set;
		}
	}
	return {isSend : isSend}
})();




// 인증번호 보내기
function sendAuthNum(data, func){
	$.ajax({
		url: "/send/authNum",
		type: "POST",
		data: data 
	})
	.then(function(result){
		swal({
			text: result
		})
		.then(function(){
			func();
		})
	})
	.fail(function(){
		alert("에러");
	})
}

	
// 인증번호 보낸 뒤 함수
function sendAuthNumFnc(inputBox){
	inputBox.prop("readonly", false);
	inputBox.focus();
	timer.start();
	authNum.isSend(true);
}


// 이메일로 인증번호 보내기
$(".auth_num_send_eemail").click(function(){
	const data = {
		email : $(".email").val(),
		username : username
	}
	if(!emailCheck(data.email)) {
		swal("이메일을 정확히 입력해주세요");
		return;
	}
	
	if(!timer.status().isStart) {
		swal('잠시 후 다시 시도해주세요');
		return;
	}
	
	const inputBox = $(this).siblings(".auth_num");
	
	// username의 이메일이 맞는지 확인 
	$.ajax({
		url: "/find/password/emailCheck",
		type: "GET",
		data : data
	})
	.then(function(result){
		if(result) {
			sendAuthNum({email : data.email}, function(){
				sendAuthNumFnc(inputBox);
			});
			
		} else {
			swal("가입하신 이메일과 일치하지 않습니다");
		}
	})
	.fail(function(){
		alert("에러");
	})
})	



// 전화번호로 인증번호 보내기
$(".auth_num_send_phone").click(function(){
	if(!timer.status().isStart) {
		swal('잠시 후 다시 시도해주세요');
		return;
	}
	
	const data = {
		phone : $(".phone").val(),
		username : username
	}
	if(!phoneCheck(data.phone)) {
		swal("전화번호를 정확히 입력해주세요");
		return;
	}
	
	const inputBox = $(this).siblings(".auth_num");
	
	// username의 전화번호가 맞는지 확인
	$.ajax({
		url: "/find/password/phoneCheck",
		type: "GET",
		data : data
	})
	.then(function(result){
		if(result) {
			sendAuthNum({phone : data.phone}, function(){
				sendAuthNumFnc(inputBox);
			});
			
		} else {
			swal("가입하신 전화번호와 일치하지 않습니다");
		}
	})
	.fail(function(){
		alert("에러");
	})
})



// 인증완료 후 함수
function authCompletion(){
	$.ajax({
		url: "/auth/completion",
		type: "POST",
	})
	.then(function(){
		location.href = "/modify/password?username=" + username;
	})
	.fail(function(result){
		swal(result.responseText);
	})
}




// 인증번호 입력 후 다음 버튼
$(".move_modify").click(function(){
	if(!authNum.isSend()) {
		swal("인증번호를 발송해주세요");
		return;
	}
	let authNumber = "";
	
	$("input[type=radio]:checked").each(function(){
		authNumber = $(this).siblings(".auth").find(".auth_num").val(); 
	})
	
	if(!authNumber) {
		return;
	}
	
	$.ajax({
		url: "/send/authNumCheck",
		type: "POST",
		data: {authNum : authNumber}
	})
	.then(function(){
		authCompletion();
	})
	.fail(function(result){
		swal(result.responseText);
	})
})

})

 

인증번호 보내기 메서드를 추가합니다

// 인증번호 보내기 페이지
@GetMapping("/find/password/auth")
public String auth(String username, HttpSession session) {
    Map<String, Object> authStatus = (Map<String, Object>) session.getAttribute("authStatus");
    if(authStatus == null || !username.equals(authStatus.get("username"))) {
        return "redirect:/find/password";
    }
    
    return "userInfo/auth";
}

주소창에 직접 주소를 입력하고 들어왔을 때 세션에 저장된 아이디가 없을경우 처음페이지로 이동합니다

 

 

 

이메일이나 전화번호를 입력하고 인증번호받기 버튼을 누르면 가입했던 아이디와 입력한 이메일과 전화번호가 일치하는지 확인합니다

// username의 이메일이 맞는지 확인
@GetMapping("/find/password/emailCheck")
public ResponseEntity<Boolean> emailCheck(String username, String email){
    boolean emailCheck = userService.emailCheck(username, email);
    return new ResponseEntity<Boolean>(emailCheck, HttpStatus.OK);
}


// username의 전화번호가 맞는지 확인
@GetMapping("/find/password/phoneCheck")
public ResponseEntity<Boolean> phoneCheck(String username, String phone) {
    boolean phoneCheck = userService.phoneCheck(username, phone);
    return new ResponseEntity<Boolean>(phoneCheck,HttpStatus.OK);
}

 

UserServiceImp

@Override
public boolean emailCheck(String username, String email) {
    Map<String, Object> map = new HashMap<>();
    map.put("username", username);
    map.put("email", email);
    String result = userDAO.emailCheck(map);
    if("1".equals(result)) {
        return true;
    }
    return false;
}


@Override
public boolean phoneCheck(String username, String phone) {
    Map<String, Object> map = new HashMap<>();
    map.put("username", username);
    map.put("phone", phone);
    System.out.println(map);
    String result = userDAO.phoneCheck(map);
    if("1".equals(result)) {
        return true;
    }
    return false;
}

 

UserDAOImp

@Override
public String emailCheck(Map<String, Object> map) {
    return sql.selectOne("user.emailCheck", map);
}

@Override
public String phoneCheck(Map<String, Object> map) {
    return sql.selectOne("user.phoneCheck", map);
}

 

UserMapper

<select id="emailCheck" resultType="String">
    SELECT	1 result 
    FROM 	DUAL 
    WHERE EXISTS(
        SELECT	1 
        FROM 	BM_USER 
        WHERE 	USERNAME = #{username } 
        AND 	EMAIL = #{email }
    )
</select>


<select id="phoneCheck" resultType="String">
    SELECT	1 result 
    FROM 	DUAL 
    WHERE EXISTS(
        SELECT	1 
        FROM 	BM_USER 
        WHERE 	USERNAME = #{username } 
        AND 	PHONE = #{phone }
    )
</select>

쿼리에서 exists를 사용해서 일치하는 아이디가 있다면 1을 출력하게 했습니다

 

overlapCheck쿼리는 count를 사용해서 중복된 아이디 수를 찾았었는데 exists로 변경하면 더 좋을거 같습니다

 

 

 

잘못된 이메일을 입력했을 때

 

가입한 이메일을 정확히 입력했을 때 이메일로 인증번호를 보냅니다

 

컨트롤러의 authNum메서드에 이메일로 인증번호 보낼 코드를 추가합니다

// 인증번호 보내기
@PostMapping("/send/authNum")
private ResponseEntity<String> authNum(String phone, String email, HttpSession session){
    String authNum = "";
    for(int i=0;i<6;i++) {
        authNum += (int)(Math.random() * 10);
    }
    
    System.out.println("인증번호 : " + authNum);
    
    // 전화번호로 인증번호 보내기 추가
    if(phone != null) {
//			System.out.println("전화번호로 인증번호 보내기");

        
        
    // 이메일로 인증번호 보내기
    } else if(email != null) {
//			System.out.println("이메일로 인증번호 보내기");
        mailService.sendAuthNum(email, authNum);
    }
    
    
    Map<String, Object> authNumMap = new HashMap<>();
    long createTime = System.currentTimeMillis(); // 인증번호 생성시간
    long endTime = createTime + (300 *1000);	// 인증번호 만료시간
    
    authNumMap.put("createTime", createTime);
    authNumMap.put("endTime", endTime);
    authNumMap.put("authNum", authNum);
    
    session.setMaxInactiveInterval(300);
    session.setAttribute("authNum", authNumMap);
    
    return new ResponseEntity<String>("인증번호가 전송되었습니다", HttpStatus.OK);
}

 

MailServiceImp에도 메서드를 추가합니다

@Override
public void sendAuthNum(String email, String authNum) {
    SimpleMailMessage simpleMailMessage = new  SimpleMailMessage();
    simpleMailMessage.setTo(email);
    simpleMailMessage.setSubject("비밀번호 찾기 인증번호");
    
    String text = "인증번호는 " + authNum + "입니다";
    
    simpleMailMessage.setText(text);
    new Thread(new Runnable() {
        public void run() {
            mailSender.send(simpleMailMessage);
        }
    }).start();
    
}

 

전송된 인증번호를 입력 후 다음 버튼을 누르면  /auth/completion를 요청하고 인증세션의 status를 true로 변경합니다

다음페이지인 비밀번호 변경페이지에서 status값이 true가 아니라면 접근할 수 없습니다

// 인증 완료 후
@PostMapping("/auth/completion")
public ResponseEntity<String> authCompletion(HttpSession session) {
    Map<String, Object> authStatus = (Map<String, Object>) session.getAttribute("authStatus");
    if(authStatus == null) {
        return new ResponseEntity<String>("인증시간이 만료되었습니다", HttpStatus.BAD_REQUEST);
    }
    authStatus.put("status", true);
    return new ResponseEntity<String>(HttpStatus.OK);
}