회원 가입 시
이메일로 인증번호 발송하는 것
DB에 비밀번호 저장 시 bcrypt 암호화
EmailServiceImpl
/** 인증번호 생성 (영어 대문자 + 소문자 + 숫자 => 6자리)
* @return authKey
*/
public String createAuthKey() {
String key = "";
for(int i = 0 ; i < 6 ; i++) {
int sel1 = (int)(Math.random() * 3);
if(sel1 == 0) {
// 숫자 0~9
int num = (int)(Math.random() * 10);
key += num;
} else {
// 영어 A~Z
char ch = (char)(Math.random() * 26 + 65);
int sel2 = (int)(Math.random() * 2);
if(sel2 == 0) {
// 대문자를 소문자로 변경
ch = (char)(ch + ('a' - 'A'));
}
key += ch;
}
}
return key;
}
templates 폴더 안에 email 폴더 안에 signUp.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<body>
<!-- css/js 파일은 이메일 첨부 불가 -->
<!-- 사용하고 싶으면 태그 안에 inline 형식으로 작성해야함 -->
<div style="display: flex; flex-direction: column; align-items: center;">
<!-- Content-ID로 등록된 이미지 중 logo를 출력 -->
<img src="cid:logo" width="200px">
<!-- service 단에서 helper 이용해서 세팅 -->
<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>
<!-- service 단에서 context.setVariavle("authKey", authKey) 값이 들어옴 -->
</h3>
</div>
</body>
</html>
이메일 안에 내용이기 때문에 head 태그는 필요 없음
EmailServiceImpl
타임리프 템플릿 엔진을 이용해서 html 코드를 java로 변환할 때 SpringTemplateEngine 필요
import org.thymeleaf.spring6.SpringTemplateEngine;
private final SpringTemplateEngine templateEngine;
Context 사용시
import org.thymeleaf.context.Context;
// HTML 파일을 읽어와 String 으로 변환해주는 메서드 (타임리프 적용)
private String loadHtml(String authKey, String htmlName) {
// org.thymeleaf.Context 선택
// thymeleaf 가 적용된 html 상에서 사용할 값을 세팅할 수 있는 객체
Context context = new Context();
// 타임리프가 적용된 HTML 에서 사용할 값 추가
context.setVariable("authKey", authKey);
// templates/email 폴더에서 htmlName 과 같은
// ~.html 파일 내용을 읽어와 String 으로 변환
return templateEngine.process("email/" + htmlName, context);
// helper 로 돌려줌 String 형으로
}
JavaMailSender 에 대한 정의 EmailConfig 클래스에
package com.project.promotion.common.config;
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;
@PropertySource("classpath:/config.properties")
@Configuration
public class EmailConfig {
// Value 어노테이션 springframework 에 있는 어노테이션 (Lombok 아님)
@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");
// SMTP 서버 인증 사용할지 말지
prop.setProperty("mail.smtp.starttls.enable", "true");
// 안정한 연결 활성화 할지 말지
prop.setProperty("mail.debug", "true");
// mail 보낼 때 debug 사용 여부
prop.setProperty("mail.smtp.ssl.trust","smtp.gmail.com");
// 신뢰할 수 있는 서버 주소 사용 smtp.gmail.com
prop.setProperty("mail.smtp.ssl.protocols","TLSv1.2");
// 버전
mailSender.setUsername(userName);
// 구글 smtp 사용자 계정
mailSender.setPassword(password);
mailSender.setHost("smtp.gmail.com");
// smtp 서버 호스트 설정
mailSender.setPort(587);
// SMTP 포트 587
mailSender.setDefaultEncoding("UTF-8");
mailSender.setJavaMailProperties(prop);
return mailSender;
}
}
MimeMessageHelper, JavaMailSender, MimeMessage 필요
정의해둔 JavaMailSender 이용
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import jakarta.mail.internet.MimeMessage;
private final JavaMailSender mailSender;
/** 이메일 인증번호 발송
* @return authKey
*/
@Override
public String sendEmail(String htmlName, String email) {
// 6자리 난수(인증 코드) 생성
String authKey = createAuthKey();
try {
// 제목
String subject = null;
switch(htmlName) {
case "signUp" :
subject = "[Promotion Site] 회원 가입 인증번호 입니다."; break;
}
// 인증 메일 보내기
// MimeMessage : Java에서 메일을 보내는 객체
MimeMessage mimeMessage = mailSender.createMimeMessage();
// MimeMessage :
// Spring 에서 제공하는 메일 발송 도우미(간단 + 타임리프)
MimeMessageHelper helper
= new MimeMessageHelper(mimeMessage, true, "UTF-8");
// 매개변수 3개 전달할 수 있음
// 1번 매개변수 : MimeMessage
// 2번 매개변수 : 파일 전송 사용 ? true / false
// 3번 매개변수 : 문자 인코딩 지정
helper.setTo(email); // 받는 사람 이메일 지정
helper.setSubject(subject); // 이메일 제목 지정
helper.setText( loadHtml(authKey, htmlName), true ); // html 보낼거임
// setText 전달인자로 String 밖에 전달 못함
// HTML 코드 해석 여부 true (innerHTML 해석)
// CID(Content-ID)를 이용해 메일에 이미지 첨부
// (파일 첨부와는 다름, 이메일 내용 자체에 사용할 이미지 첨부)
// logo 추가 예정
helper.addInline("logo", new ClassPathResource("static/images/logo.jpg"));
// -> 로고 이미지를 메일 내용에 첨부하는데
// 사용하고 싶으면 "logo"라는 id를 작성해라
// 메일 보내기
mailSender.send(mimeMessage);
} catch (Exception e) {
e.printStackTrace();
return null;
}
// 이메일 + 인증 번호를 "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) 1번 update 실패 시 insert 시도
if(result == 0) {
result = mapper.insertAuthKey(map);
}
// 수정, 삽입 후에도 result 가 0 == 실패 (뭔가 잘못된 거)
if(result == 0) return null;
// 성공
return authKey; // 오류 없이 완료되면 authKey 반환
}
SecurityConfig 클래스에 정의
package com.project.promotion.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
MemberServiceImpl
@Autowired
private BCryptPasswordEncoder bcrypt;
/** 회원 가입 서비스
* @return result
*/
@Override
public int signUp(Member inputMember) {
// 비밀 번호 암호화
String encPw = bcrypt.encode(inputMember.getMemberPw());
inputMember.setMemberPw(encPw);
return mapper.signUp(inputMember);
}
gardle에 추가해줌
// Spring AOP + AspectJ
implementation 'org.springframework.boot:spring-boot-starter-aop'
spring-boot-starter-aop 의존성을 추가했을 때 에러가 사라졌다면, 그 이유는 여러 가지가 있을 수 있습니다. 이 경우, Spring AOP 관련 의존성이 추가되면서 다른 종속성들이 정상적으로 로드되었거나, 특정 문제를 해결하는 데 도움을 주었을 수 있습니다. 아래는 가능한 이유들입니다:
따라서, 문제가 해결된 이유는 AOP 관련 의존성이 프로젝트 내 다른 필요한 설정을 올바르게 활성화했기 때문일 것입니다. AOP가 실제로 코드에서 사용되지 않더라도, 관련 의존성을 추가하는 것만으로도 종속성 문제가 해결된 경우일 수 있습니다.