fetch("/email/signup", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : memberEmail.value
})
@Autowired 를 이용한 의존성 주입 방법은 3가지 존재
@Autowired
private EmailService service;
의존성 주입(DI)
private EmailService service;
@Autowired
public void setService(EmailService service) {
this.service = service;
}
private EmailService es;
private MemberService ms;
@Autowired
public EmailController(EmailService es, MemberService, MemberService ms) {
this.es = es;
this.ms = ms;
}
@RequiredArgsConstructor 를 이용하면 필드 중
1. 초기화 되지 않은 final이 붙은 필드
2. 초기화 되지 않은 @NotNull 이 붙은 필드
위 둘에 해당하는 필드에 대한 @Autowired 생성자 구문을 자동 완성해주는 어노테이션(내부적으로)
EmailController 권장되는 방식으로 @Autowired 생성되는 거
@Controller
@RequestMapping("email")
@RequiredArgsConstructor
public class EmailController {
private final EmailService service;
@ResponseBody
@PostMapping("signup")
public int signup(@RequestBody String email) {
String authKey = service.sendEmail("signup", email);
return 0;
}
}
service 호출할 때 보낸 "signup"은 key 역할을 할 문자열
이메일 보낼 때 html 형식으로 보내줄건데 그 html 이름을 signup이라고 해주겠다는 키
EmailServiceImpl
/** 인증번호 생성 (영어 대문자 + 소문자 + 숫자 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;
}
SMTP (Simple Mail Transfer Protocol) 간단한 메일 전송 규약
--> 이메일 메세지를 보내고 받을 때 사용하는 약속(규약, 방법)
Google SMTP
Java Mail Sender 모듈 필요 (build.gradle 모듈 추가해둠)
implementation 'org.springframework.boot:spring-boot-starter-mail'
Java Mail Sender -> Google SMTP -> 대상에게 이메일 전송
Google SMTP에 사용할 username, password
spring.mail.username=계정이메일
spring.mail.password=앱비밀번호
앱비밀번호는 공백없이 작성
@Value : properties 에 작성된 내용 중 키가 일치하는 값을 얻어와 필드에 대입
org.springframework.beans.factory.annotation.Value;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@Configuration
@PropertySource("classpath:/config.properties")
public class EmailConfig {
@Value("${spring.mail.username}")
private String userName;
@Value("${spring.mail.password}")
private String password;
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
// mailSender 에서 이용할 각종 설정들 작성
Properties prop = new Properties();
// 전송 프로토콜 설정
prop.setProperty("mail.transport.protocol", "smtp");
// SMTP 서버 인증 사용할지 말지
prop.setProperty("mail.smtp.auth", "true");
// 안정한 연결 활성화 할지 말지
prop.setProperty("mail.smtp.starttls.enable", "true");
// mail 보낼 때 debug 사용 여부
prop.setProperty("mail.debug", "true");
// 신뢰할 수 있는 서버 주소 사용 smtp.gmail.com
prop.setProperty("mail.smtp.ssl.trust","smtp.gmail.com");
// 버전
prop.setProperty("mail.smtp.ssl.protocols","TLSv1.2");
// 구글 smtp 사용자 계정
mailSender.setUsername(userName);
mailSender.setPassword(password);
// smtp 서버 호스트 설정
mailSender.setHost("smtp.gmail.com");
// SMTP 포트 587
mailSender.setPort(587);
mailSender.setDefaultEncoding("UTF-8");
mailSender.setJavaMailProperties(prop);
return mailSender;
}
}
EmailServiceImpl
// EmailConfig 설정이 적용된 객체(메일 보내기 기능)
private final JavaMailSender mailSender;
JavaMailSender 는 위에 EmailConfig에 만들어둠
resources/templates/email/signup.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<!-- 메일은 header 불필요 -->
<body>
<!-- css/js 파일은 이메일 첨부 불가 -->
<!-- 태그 안에 inline 형식으로 작성 -->
<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>
th:text 안에 authKey 는 serviceImpl 에서 세팅해줄 거
타임리프(템플릿 엔진)를 이용해서 html 코드 -> java 코드로 변환
private final SpringTemplateEngine templateEngine;
필드에 작성
// HTML 파일을 읽어와 String 으로 변환 (타임리프 적용)
private String loadHtml(String authKey, String htmlName) {
// org.thymeleaf.Context. 선택
Context context = new Context();
// thymeleaf 가 적용된 html 상에서 값을 세팅할 수 있는 객체
// 타임리프가 적용된 HTML 에서 사용할 값을 추가
context.setVariable("authKey", authKey);
// 어떤 HMTL 을 해석하고 있는지도 알려줘야함
return templateEngine.process("email/" + htmlName, context);
// templates/email 폴더에서 htmlName 과 같은 .html 파일 내용을 읽어와서 String 으로 변환
// , context 는 authKey 세팅해둔 거 보내주는 거
}
EmailServiceImpl
// 이메일 보내기
@Override
public String sendEmail(String htmlName, String email) {
// 6자리 난수 생성
String authKey = createAuthKey();
// 이메일 보낼 때 Exception 발생할 수 있어서 try catch
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); // 이메일 제목 지정
// html
helper.setText( loadHtml(authKey, htmlName), true);
// HTML 코드 해석 여부 true (innerHTML 해석)
// CID(Content-ID)를 이용해 메일에 이미지 첨부
// (파일첨부와는 다름, 이메일 내용 자체에 사용할 이미지 첨부)
// logo 추가예정
helper.addInline("logo", new ClassPathResource("static/images/logo.jpg")); // html 에서 cid:logo 로 부름
// -> 로고 이미지를 메일 내용에 첨부하는데
// 사용하고 싶으면 "logo"라는 id 를 작성해라
// classpath 는 resources
// 메일 보내기
mailSender.send(mimeMessage);
log.debug("인증번호 : " + authKey);
} catch (Exception e) {
e.printStackTrace();
return null;
}
7번 내용
}
메일 받은 화면

EmailServiceImple
// 이메일 + 인증번호를 "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);
if(result == 0) {
// 업데이트 실패 (인증번호 처음) INSERT 시도
result = mapper.insertAuthKey(map);
}
// INSERT 후 result 가 0이면 실패, 1이면 성공
if(result == 0) return null;
// 성공
return authKey; // 오류없이 완료되면 인증번호 반환
}
email-mapper.xml
<!-- 인증번호 수정 -->
<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>
EmailController
if(authKey != null) { // 인증번호가 반환돼서 돌아옴
// 이메일 보내기 성공
return 1;
}
// 이메일 보내기 실패
return 0;
signup.js
fetch() 아래
.then(resp => resp.text())
.then(result => {
if(result == 1) {
console.log("인증 번호 발송 성공");
} else {
console.log("인증 번호 발송 실패");
}
});
결과 확인


인증번호 입력 후 인증하기 버튼 누르면 DB 에 들어온 번호와 같은지 확인하고 처리
signup.js
checkAuthKeyBtn.addEventListener("click", () => {
// 타이머가 00:00 인 경우
if(min === 0 && sec === 0) {
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 => {
if(result == 0) {
alert("인증번호가 일치하지 않습니다.");
checkObj.authKey = false;
return;
}
clearInterval(authTimer); // 타이머 멈춤
authKeyMessage.innerText = "인증 되었습니다.";
authKeyMessage.classList.remove('error');
authKeyMessage.classList.add('confirm');
checkObj.authKey = true; // 인증 번호 검사여부 true
});
});
EmailController
@ResponseBody
@PostMapping("checkAuthKey")
public int checkAuthKey(@RequestBody Map<String, Object> map) {
// 입력 받은 이메일, 인증번호가 DB에 있는지 조회
// 이메일 있고, 인증번호 일치 == 1
// 아니면 0
return service.checkAuthKey(map);
}
EmailServiceImpl
@Override
public int checkAuthKey(Map<String, Object> map) {
return mapper.checkAuthKey(map);
}
EmailMapper interface
int checkAuthKey(Map<String, Object> map);
email-mapper.xml
<select id="checkAuthKey" resultType="_int">
SELECT COUNT(*)
FROM "TB_AUTH_KEY"
WHERE EMAIL = #{email}
AND AUTH_KEY = #{authKey}
</select>
결과 확인