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);
}
'스프링부트' 카테고리의 다른 글
sts4 깃 임포트 후 db 연결 에러 (0) | 2022.01.07 |
---|---|
스프링부트+jsp로 배달사이트 만들기-39 비밀번호 찾기, 비밀번호 변경 (0) | 2022.01.04 |
스프링부트+jsp로 배달사이트 만들기-37 내 정보 수정하기 (0) | 2021.12.27 |
스프링부트+jsp로 배달사이트 만들기-36 카카오아이디로 로그인 (0) | 2021.12.23 |
스프링부트+jsp로 배달사이트 만들기-35 네이버아이디로 로그인 (0) | 2021.12.23 |