사이트마다 회원가입 시 이메일 인증을 요구로 한다. 따라서 나도 이메일 인증을 구현 하기로 했다.
1. 앱 선택 - 기기 선택 - 생성버튼
이전에는 보안 수준이 낮은 앱의 액세스 허용을 통해서 SMTP를 사용할 수 있었다. 하지만 요즘에는 구글이 비밀번호 2단계 인증을 사용하면서도 SMTP를 사용할 수 있도록 앱 비밀번호 라는 것을 만들었다.
앱 비밀번호는 나중에 config를 설정할 때 이메일 패스워드 대신 사용하게 될 것이다.
//spring-boot-starter-mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
// mail 서포트
implementation 'org.springframework:spring-context-support:5.3.24'
//javax.mail
implementation 'com.sun.mail:javax.mail:1.6.2'
<div class="form-floating mb-1">
<input type="text" class="form-control" id="email" name="email" placeholder="email">
<label for="email">이메일</label>
</div>
<div class="form-floating mb-1">
<button type="button" class="btn btn-outline-secondary" id="mail-check-btn">본인인증</button>
</div>
<div class="form-floating mb-1">
<input type="text" class="form-control" id="email-auth" name="email-auth" placeholder="인증번호 6자리를 입력해주세요." disabled style="background-color: #b3b2b217">
<p id="mail-check-warn" class="mail-check-warn"></p>
</div>
// 이메일 인증
$('#mail-check-btn').click(function() {
const email = $('#email')[0].value; // 이메일 주소값 얻어오기!
console.log('완성된 이메일 : ' + email); // 이메일 오는지 확인
const checkInput = $('#email-auth'); // 인증번호 입력하는곳
$.ajax({
type : "GET",
url : "/account/email?email=" + email, // GET방식이라 Url 뒤에 email을 붙힐수있다.
success : function (data) {
console.log("아래는 서버에서 전달 받은 데이터 값");
console.log("data : " + data);
checkInput.attr('disabled',false);
checkInput.css('background', 'white');
code =data;
alert('인증번호가 전송되었습니다.');
console.log("인증번호 전송까지는 오케");
}
}); // end ajax
}); // end send eamil
// 인증번호 비교
// blur -> focus가 벗어나는 경우 발생
$('#email-auth').blur(function () {
console.log("blur 작동은 하나?")
const inputCode = $('#email-auth')[0].value;
console.log("inuptCode : " + inputCode);
const resultMsg = $('#mail-check-warn')[0];
if(inputCode === code){
console.log("code : " + code);console.log("inputCode : " + inputCode);
resultMsg.innerText = '인증번호가 일치합니다.';
resultMsg.style.color = 'green';
$('#mail-check-btn').disabled = true;
$('#email').readonly = true;
// $('#userEmail2').attr('onFocus', 'this.initialSelect = this.selectedIndex');
// $('#userEmail2').attr('onChange', 'this.selectedIndex = this.initialSelect');
}else{
resultMsg.html('인증번호가 불일치 합니다. 다시 확인해주세요!.');
resultMsg.style.color = 'red';
}
});
// 이메일 인증
@GetMapping(value = "/email")
@ResponseBody
public String checkEmail(String email) {
System.out.println("받은 이메일 : " + email);
String data = emailAuthService.joinEmail(email);
return data;
}
public interface EmailService {
String sendSimpleMessage(String to)throws Exception;
}
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Random;
@Service
@RequiredArgsConstructor
public class EmailAuthService implements EmailService{
@Autowired
private JavaMailSender mailSender;
private int authNumber; // 난수 발생
public void makeRandomNumber() {
// 난수의 범위 111111 ~ 999999 (6자리 난수)
Random r = new Random();
int checkNum = r.nextInt(888888) + 111111;
System.out.println("인증번호 : " + checkNum);
authNumber = checkNum;
}
//이메일 보낼 양식!
public String joinEmail(String email) {
makeRandomNumber();
String setFrom = "qhtmf2059@gmail.com"; // email-config에 설정한 자신의 이메일 주소를 입력
String toMail = email;
String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목
String content =
"당근마켓 클론코딩 홈페이지를 방문해주셔서 감사합니다." + //html 형식으로 작성 !
"<br><br>" +
"인증 번호는 " + authNumber + "입니다." +
"<br><br>" +
"회원가입 페이지로 돌아가 회원가입을 마치시기 바랍니다."; // 이메일 내용
System.out.println("메일 데이터 : " + title);
System.out.println("메일 데이터 : " + content);
System.out.println("메일 데이터 : " + setFrom);
mailSend(setFrom, toMail, title, content);
return Integer.toString(authNumber);
}
//이메일 전송 메소드
public void mailSend(String setFrom, String toMail, String title, String content) {
System.out.println("mailSend 확인 완료");
MimeMessage mailMessage = mailSender.createMimeMessage();
try {
mailMessage.addRecipients(MimeMessage.RecipientType.TO, toMail);
mailMessage.setSubject(title);
mailMessage.setFrom(setFrom);
mailMessage.setText(content, "utf-8", "html");
System.out.println("mailSend 메일 보내기 1초 전");
mailSender.send(mailMessage);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
@Override
public String sendSimpleMessage(String to) throws Exception {
return null;
}
}
mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465
# admin
AdminMail.id = 여러분의이메일
AdminMail.password = 여러분의앱비밀번호
중요!! properties 위치는 resource 아래에 둔다.
AdminMail.id를 적을 때 작은 따옴표나 큰 따옴표는 굳이 안 적어도 된다.
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:email.properties")
public class EmailConfig {
@Value("${mail.smtp.port}")
private int port;
@Value("${mail.smtp.socketFactory.port}")
private int socketPort;
@Value("${mail.smtp.auth}")
private boolean auth;
@Value("${mail.smtp.starttls.enable}")
private boolean starttls;
@Value("${mail.smtp.starttls.required}")
private boolean startlls_required;
@Value("${mail.smtp.socketFactory.fallback}")
private boolean fallback;
@Value("${AdminMail.id}")
private String id;
@Value("${AdminMail.password}")
private String password;
@Bean
public JavaMailSender javaMailService() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost("smtp.gmail.com");
javaMailSender.setUsername(id);
javaMailSender.setPassword(password);
javaMailSender.setPort(port);
javaMailSender.setJavaMailProperties(getMailProperties());
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
private Properties getMailProperties()
{
Properties pt = new Properties();
pt.put("mail.smtp.socketFactory.port", socketPort);
pt.put("mail.smtp.auth", auth);
pt.put("mail.smtp.starttls.enable", starttls);
pt.put("mail.smtp.starttls.required", startlls_required);
pt.put("mail.smtp.socketFactory.fallback",fallback);
pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
return pt;
}
}
EmailConfig에서 properties에 작성한 정보를 가져와 JavaMailSender의 빈을 등록한다.
처음에 @bean을 등록하지 않은 채로 Service에서 JavaMailSender에 @Autowired를 작성해서 오류가 났었다. 의존성 주입(DI)의 기본 중에 기본인데 아차 싶었다.
맨날 @Service, @Controller, @Repository 만 사용하다 보니 @Bean 등록을 잊었던 것 같다.
(참고로 @Service와 같이 @Component를 포함하는 어노테이션은 자동으로 Bean을 등록해주고 @Autowired를 작성하면 필드에 빈을 자동으로 주입을 해준다. )
참고 블로그
https://javaju.tistory.com/100
https://devofroad.tistory.com/43
java.util.ServiceConfigurationError: javax.xml.ws.spi.Provider: Provider org.jboss.ws.core.jaxws.spi.ProviderImpl not a subtype
모든 게 다 끝났다고 생각했을 때 위와 같은 오류가 났다.
여러 블로그를 참고하면서 구현하다보니 라이브러리를 막무가내로 집어넣어서 생긴 오류였다.
아래 링크에 올라온 글의 작성이 또한 라이브러리의 충돌 때문에 이 오류가 생겼다고 한다.
참고 글
https://stackoverflow.com/questions/24624719/exception-coming-java-util-serviceconfigurationerror
글을 보고 설마.. 하는 마음에 인텔리제이 라이브러리 재설정 등을 쳐봤다.
File - Invalidate Caches를 통해 캐쉬를 삭제할 수 있다고 한다.
해봤더니 드디어 이메일 전달이 됐다.
여러 블로그를 참고하면서 다 조금씩 다른 라이브러리를 추가하는 걸 보고, 그냥 일단 넣고 사용 안 하는 건 지우면 되지.. 이런 생각이 위와 같은 오류를 발생시켰던 것 같다.
앞으로는 라이브러리를 넣을 때 충돌이 일어날 수도 있다는 사실을 염두하며 막무가내로 넣는 행동은 하지 말아야겠다😅