
✅ 회원가입 유효성 검사 여부 체크 객체
// **** 회원 가입 유효성 검사 ****
// 필수 입력 항목의 유효성 검사 여부를 체크하기 위한 객체
// - true == 해당 항목은 유효한 형식으로 작성됨
// - false == 해당 항목은 유효하지 않은 형식으로 작성됨
const checkObj = {
"memberEmail" : false,
"memberPw" : false,
"memberPwConfirm" : false,
"memberNickname" : false,
"memberTel" : false,
"authKey" : false,
}
/** 회원 가입 페이지 이동
* @return
*/
@GetMapping("signup")
public String signupPage() {
return "member/signup";
}
@ResponseBody // 응답 본문(요청한 fetch)으로 돌려보냄
@GetMapping("checkEmail")
public int checkEmail(@RequestParam("memberEmail") String memberEmail) {
return service.checkEmail(memberEmail);
}
/* 이메일 유효성 검사 */
// 1) 이메일 유효성 검사에 사용될 요소 얻어오기
const memberEmail = document.querySelector("#memberEmail");
const emailMessage = document.querySelector("#emailMessage");
// 2) 이메일이 입력될 때 마다 유효성 검사 수행
memberEmail.addEventListener("input", e => {
// 이메일 인증 후 이메일이 변경될 경우
checkObj.authKey = false;
document.querySelector("#authKeyMessage").innerText ="";
// 작성된 이메일 값 얻어오기
const inputEmail = e.target.value;
// 3) 입력된 이메일이 없을 경우
if(inputEmail.trim().length === 0) {
emailMessage.innerText = "메일을 받을 수 있는 이메일을 입력해주세요.";
// 메시지에 색상을 추가하는 클래스 모두 제거
emailMessage.classList.remove('confirm', 'error');
// 이메일 유효성 검사 여부를 false 변경
checkObj.memberEmail = false;
// 잘못 입력한 띄어쓰기가 있을 경우 없앰
memberEmail.value = "";
return;
}
// 4) 입력된 이메일이 있을 경우 정규식 검사
// (알맞은 형태로 작성했는지 검사)
const regExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// 입력 받은 이메일이 정규식과 일치하지 않는 경우
// (알맞은 이메일 형태가 아닌 경우)
if( !regExp.test(inputEmail) ) {
emailMessage.innerText = "알맞은 이메일 형식으로 작성해주세요.";
emailMessage.classList.add('error'); // 글자를 빨간색으로 변경
emailMessage.classList.remove('confirm'); // 초록색 글자 제거
checkObj.memberEmail = false; // 유효하지 않은 이메일임을 기록
return;
}
// 5) 유효한 이메일 형식일 경우 중복 검사 수행
// 비동기(ajax)
fetch("/member/checkEmail?memberEmail=" + inputEmail)
.then( resp => resp.text() )
.then( count => {
// count : 1이면 중복, 0이면 중복 아님
if(count == 1) { // 중복 O
emailMessage.innerText = "이미 사용중인 이메일입니다.";
emailMessage.classList.add('error');
emailMessage.classList.remove('confirm');
checkObj.memberEmail = false; // 중복은 유효하지 않음
return;
}
// 중복 X 경우
emailMessage.innerText = "사용 가능한 이메일입니다.";
emailMessage.classList.add('confirm');
emailMessage.classList.remove('error');
checkObj.memberEmail = true; // 유효한 이메일
})
.catch(error => {
// fetch() 수행 중 예외 발생 시 처리
console.log(error); // 발생한 예외 출력
});
});



기재 생략 사항(보안 및 아직 미사용)
config.properties
EmailMapper, email-mapper.xml
@Configuration
@PropertySource("classpath:/config.properties")
public class EmailConfig {
// @Value : properties에 작성된 내용 중 키가 일치하는 값을 얻어와 필드에 대입
@Value("${spring.mail.username}")
private String userName;
@Value("${spring.mail.password}")
private String password;
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
Properties prop = new Properties();
prop.setProperty("mail.transport.protocol", "smtp"); // 전송 프로토콜
prop.setProperty("mail.smtp.auth", "true"); // 인증 사용 여부
prop.setProperty("mail.smtp.starttls.enable", "true"); // 안전한 연결 활성화 여부
prop.setProperty("mail.debug", "true"); // 디버그 사용 여부
prop.setProperty("mail.smtp.ssl.trust","smtp.gmail.com"); // 신뢰할 수 있는 SMTP 서버 호스트 지정
prop.setProperty("mail.smtp.ssl.protocols","TLSv1.2"); // 버전 설정
mailSender.setUsername(userName);
mailSender.setPassword(password);
mailSender.setHost("smtp.gmail.com"); // SMTP 서버 호스트 설정
mailSender.setPort(587); // SMTP 서버 포트 설정
mailSender.setDefaultEncoding("UTF-8"); // 기본 인코딩 설정
mailSender.setJavaMailProperties(prop); // 위에 정의한 prop 세팅
return mailSender;
}
}
@Controller
@RequestMapping("email")
@RequiredArgsConstructor // final 필드 / @NotNull 필드에 자동으로 의존성 주입(@Autowired 생성자 방식 코드 자동완성)
public class EmailController {
private final EmailService service;
@ResponseBody
@PostMapping("signup")
public int signup(@RequestBody String email) {
String authKey = service.sendEmail("signup", email);
return 0;
}
}
/* @Autowired를 이용한 의존성 주입 방법은 3가지 존재
* 1) 필드
* 2) Setter
* 3) 생성자 (권장)
*
* Lombok 라이브러리에서 제공하는
*
* @RequiredArgsConstructor 를 이용하면
*
* 필드 중
* 1) 초기화 되지 않은 final이 붙은 필드
* 2) 초기화 되지 않은 @NotNull이 붙은 필드
*
* 1, 2에 해당하는 필드에 대한
* @Autowired 생성자 구문을 자동 완성
*
*/
// 1) 필드에 의존성 주입(권장 X)
// @Autowired
// private EmailService service;
// 2) Setter 이용
// private EmailService service;
// @Autowired
// public void setService(EmailService service) {
// this.service = service;
// }
// 3) 생성자
// private EmailService service;
// private MemberService service2;
// @Autowired
// public EmailController(EmailService service, MemberService service2) {
// this.service = service;
// this.service = service2;
// }
@Transactional // 예외 발생하면 롤백할게~ (기본값 커밋)
@Service
@RequiredArgsConstructor
public class EmailServiceImpl implements EmailService {
// EmailConfig 설정이 적용된 객체(메일 보내기 기능)
private final JavaMailSender mailSender;
// Mapper 의존성 주입
private final EmailMapper mapper;
// 이메일 보내기
@Override
public String sendEmail(String htmlName, String email) {
// 6자리 난수(인증 코드) 생성
String authKey = createAuthKey();
try {
// 제목
String subject = null;
switch(htmlName) {
case "signup" :
subject = "[boardProject] 회원가입 인증번호입니다."; break;
}
// 인증 메일 보내기
// MimeMessage : Java에서 메일을 보내는 객체
MimeMessage mimeMessage = mailSender.createMimeMessage();
// MimeMessageHelper : Spring에서 제공하는 메일 발송 도우미(간단 + 타임리프)
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
// 1번 매개변수 : MimeMessage
// 2번 매개변수 : 파일 전송 사용 ? true / false
// 3번 매개변수 : 문자 인코딩 지정
helper.setTo(email); // 받는 사람 이메일 지정
helper.setSubject(subject); // 이메일 제목 지정
helper.setText(authKey); // html보낼거임(추후 변경 예정)
// CID(Content-ID)를 이용해 메일에 이미지 첨부
// logo 추가 예정
// 메일 보내기
mailSender.send(mimeMessage);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
/** 인증번호 생성 (영어 대문자 + 소문자 + 숫자 6자리)
* @return authKey
*/
public String createAuthKey() {
String key = "";
for(int i=0 ; i< 6 ; i++) {
int sel1 = (int)(Math.random() * 3); // 0:숫자 / 1,2:영어
if(sel1 == 0) {
int num = (int)(Math.random() * 10); // 0~9
key += num;
}else {
char ch = (char)(Math.random() * 26 + 65); // A~Z
int sel2 = (int)(Math.random() * 2); // 0:소문자 / 1:대문자
if(sel2 == 0) {
ch = (char)(ch + ('a' - 'A')); // 대문자로 변경
}
key += ch;
}
}
return key;
}
}
/* Google SMTP를 이용한 이메일 전송하기
*
* * SMTP(Simple Mail Transfer Protocol, 간단한 메일 전송 규약)
* - 이메일 메시지를 보내고 받을 때 사용하는 약속(규약, 방법)
*
* - Google SMTP
*
* Java Mail Sender -> Google SMTP -> 대상에게 이메일 전송
*
* * Java Mail Sender에 Google SMTP 이용 설정 추가
* 1) config.properties 내용 추가(계정, 앱비밀번호)
* 2) EmailConfig.java
*/
// 1. 변수 세팅
// 인증번호 받기 버튼
const sendAuthKeyBtn = document.querySelector("#sendAuthKeyBtn");
// 인증번호 입력 input
const authKey = document.querySelector("#authKey");
// 인증번호 입력 후 확인 버튼
const checkAuthKeyBtn = document.querySelector("#checkAuthKeyBtn");
// 인증번호 관련 메시지 출력 span
const authKeyMessage = document.querySelector("#authKeyMessage");
let authTimer; // 타이머 역할을 할 setInterval을 저장할 변수
const initMin = 4; // 타이머 초기값 (분)
const initSec = 59; // 타이머 초기값 (초)
const initTime = "05:00";
// 실제 줄어드는 시간을 저장할 변수
let min = initMin;
let sec = initSec;
// 2. 인증번호 받기 버튼 클릭 시 이벤트 생성
sendAuthKeyBtn.addEventListener("click", () => {
// 재클릭에 대한 처리
checkObj.authKey = false;
authKeyMessage.innerText = "";
// 중복되지 않은 유효한 이메일을 입력한 경우가 아니면
if( !checkObj.memberEmail ) {
alert("유효한 이메일 작성 후 클릭해 주세요.");
return;
}
// 클릭시 타이머 숫자 초기화
min = initMin;
sec = initSec;
// 이전 동작 중인 인터벌 클리어
clearInterval(authTimer);
// ***************************************************
// 비동기로 서버에서 메일 보내기
fetch("/email/signup", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : memberEmail.value
})
// ***************************************************
// 메일은 비동기로 서버에서 보내라고 하고
// 화면에서는 타이머 시작하기
authKeyMessage.innerText = initTime; // 05:00 세팅
authKeyMessage.classList.remove('confirm', 'error'); // 검정 글씨
alert("인증번호가 발송되었습니다.")
// SetInterval(함수, 지연시간(ms))
// - 지연시간(ms)만큼 시간이 지날 때 마다 함수 수행
// clearInterval(Interval이 저장된 변수)
// - 매개변수로 전달받은 interval을 멈춤
// 인증 시간 출력(1초마다 동작)
authTimer = setInterval( () => {
authKeyMessage.innerText = `${addZero(min)}:${addZero(sec)}`;
// 0분 0초인 경우 ("00:00" 출력 후)
if(min == 0 && sec == 0) {
checkObj.authKey = false; // 인증 못함
clearInterval(authTimer); // 인터벌 멈춤
authKeyMessage.classList.add('error');
authKeyMessage.classList.remove('confirm');
return;
}
// 0초인 경우(0초를 출력한 후)
if(sec == 0) {
sec = 60;
min--;
}
sec--; // 1초 감소
} , 1000); // 1초 지연시간
});
// 전달 받은 숫자가 10 미만인 경우(한자리) 앞에 0 붙여서 반환
function addZero(number) {
if(number < 10) return "0" + number;
else return number;
}


helper.setText( loadHtml(authKey, htmlName), true ); // html보낼거임(추후 변경 예정)
// HTML 코드 해석 여부 true (innerHTML 해석)
// CID(Content-ID)를 이용해 메일에 이미지 첨부
// (파일첨부와는 다름, 이메일 내용 자체에 사용할 이미지 첨부)
// logo 추가 예정
helper.addInline("logo", new ClassPathResource("static/images/logo.jpg"));
// -> 로고 이미지를 메일 내용에 첨부하는데
// 사용하고 싶으면 "logo"라는 id를 작성해라
// 타임리프(템플릿 엔진)을 이용해서 html 코드 -> java로 변환
private final SpringTemplateEngine templateEngine;
// HTML 파일을 읽어와 String으로 변환(타임리프 적용)
private String loadHtml(String authKey, String htmlName) {
// org.thymeleaf.context 선택
Context context = new Context();
// 타임리프가 적용된 HTML에서 사용할 값 추가
context.setVariable("authKey", authKey);
// templates/email 폴더에서 htmlName과 같은
// ~.html 파일 내용을 읽어와 String으로 변환
return templateEngine.process("email/" + htmlName, context);
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 메인은 header 불필요 -->
<body>
<!-- css/js 파일은 이메일 첨부 불가 -->
<div style="display: flex; flex-direction: column; align-items: center;">
<!-- Content-ID로 등록된 이미지 중 logo를 출력 -->
<img src="cid:logo" width="200px">
<h3>
아래 발급된 인증 번호를 제한 시간 내에
<br>
회원 가입 화면에 입력 후 인증 버튼을 눌러주세요.
</h3>
<h3 style="text-align: center; border: 3px solid black;
color: blue; width: 400px; padding: 10px;">
<th:block th:text="${authKey}"></th:block>
</h3>
</div>
</body>
</html>

// 이메일 + 인증번호를 "TB_AUTH_KEY" 테이블 저장
Map<String, String> map = new HashMap<>();
map.put("authKey", authKey);
map.put("email", email);
// 1) 해당 이메일이 DB에 존재하는 경우가 있을 수 있기 때문에
// 수정(update)을 먼저 수행
// -> 1 반환 == 업데이트 성공 == 이미 존재해서 인증번호 변경
// -> 0 반환 == 업데이트 실패 == 이메일 존재 X --> INSERT 시도
int result = mapper.updateAuthKey(map);
// 2) update 실패 시 insert 시도
if(result == 0) {
result = mapper.insertAuthKey(map);
}
// 수정, 삽입 후에도 result 가 0 == 실패
if(result == 0) return null;
// 성공
return authKey; // 오류 없이 완료되면 authKey 반환
<!-- 인증 번호 수정 -->
<update id="updateAuthKey">
UPDATE "TB_AUTH_KEY" SET
"AUTH_KEY" = #{authKey},
"CREATE_TIME" = SYSDATE
WHERE "EMAIL" = #{email}
</update>
<!-- 인증 번호 삽입 -->
<insert id="insertAuthKey">
INSERT INTO "TB_AUTH_KEY"
VALUES(SEQ_KEY_NO.NEXTVAL, #{email}, #{authKey}, DEFAULT)
</insert>
private final EmailService service;
@ResponseBody
@PostMapping("signup")
public int signup(@RequestBody String email) {
String authKey = service.sendEmail("signup", email);
if(authKey != null) { // 인증번호가 반환되서 돌아옴
// == 이메일 보내기 성공
return 1;
}
// 이메일 보내기 실패
return 0;
}
// ***************************************************
// 비동기로 서버에서 메일 보내기
fetch("/email/signup", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : memberEmail.value
})
.then( resp => resp.text())
.then( result => {
if(result == 1) {
console.log("인증 번호 발송 성공");
} else {
console.log("인증 번호 발송 실패");
}
});
// ***************************************************


💡 구글은 display: flex; 이후 스타일이 먹지 않음
✋ java - map 전달 / js - 객체 생성 포인트
// 입력받은 이메일, 인증번호로 객체 생성 const obj = { "email" : memberEmail.value, "authKey" : authKey.value }
// 인증하기 버튼 클릭 시
// 입력된 인증번호를 비동기로 서버에 전달
// -> 입력된 인증번호와 발급된 인증번호가 같은지 비교
// 같으면 1, 아니면 0 반환
// 단, 타이머가 00:00초가 아닐 경우에만 수행
checkAuthKeyBtn.addEventListener("click", () => {
if( min === 0 && sec === 0) { // 타이머가 00:00인 경우
alert("인증번호 입력 제한시간을 초과하였습니다!");
return;
}
if(authKey.value.length < 6) {
alert("인증번호를 정확히 입력해 주세요.");
return;
}
// 입력받은 이메일, 인증번호로 객체 생성
const obj = {
"email" : memberEmail.value,
"authKey" : authKey.value
}
fetch("/email/checkAuthKey", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(obj) // obj를 JSON으로 변경
})
.then(resp => resp.text())
.then(result => {
// == : 값 비교 "1" == 1 -> true
// === : 값 + 타입비교 "1" == 1 -> false
if(result == 0) {
alert("인증번호가 일치하지 않습니다.");
checkObj.authKey = false;
return;
}
clearInterval(authTimer); // 타이머 멈춤
authKeyMessage.innerText = "인증되었습니다.";
authKeyMessage.classList.remove('error');
authKeyMessage.classList.add('confirm');
checkObj.authKey = true; // 인증 번호 검사여부 true
});
});
@ResponseBody
@PostMapping("checkAuthKey")
public int checkAuthKey(@RequestBody Map<String, Object> map) {
// 입력 받은 이메일, 인증번호가 DB에 있는지 조회
// 이메일 있고, 인증번호 일치 == 1
// 아니면 0
return service.checkAuthKey(map);
}
<!-- 이메일, 인증번호 확인 -->
<select id="checkAuthKey" resultType="_int">
SELECT COUNT(*)
FROM "TB_AUTH_KEY"
WHERE EMAIL = #{email}
AND AUTH_KEY = #{authKey}
</select>

