02_PromotionSite_회원 가입

송지윤·2024년 7월 24일

개인프로젝트

목록 보기
1/7

회원 가입 시
이메일로 인증번호 발송하는 것
DB에 비밀번호 저장 시 bcrypt 암호화

이메일로 인증번호 발송

  1. authKey를 생성

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;
		
	}
  1. 보낼 HTML 파일 값을 넣어 작성해줌

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 형으로
	}
  1. 만들어둔 loadHtml 메서드와 createAuthKey 메서드를 sendEmail 메서드에서 호출해서 사용할 것

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 관련 의존성이 추가되면서 다른 종속성들이 정상적으로 로드되었거나, 특정 문제를 해결하는 데 도움을 주었을 수 있습니다. 아래는 가능한 이유들입니다:

  1. Spring AOP 의존성에 의한 전반적인 설정 문제 해결
    spring-boot-starter-aop 의존성은 AOP 관련 기능을 제공할 뿐만 아니라, 내부적으로 AOP 관련 기능이 필요한 다른 Spring Boot 기능들도 함께 정상적으로 작동하게 만듭니다. 즉, Spring Boot의 설정이나 기본적인 Bean 등록 등에 관련된 문제가 해결될 수 있습니다.
    예를 들어, JavaMailSender와 같은 메일 관련 설정을 포함한 종속성들이 AOP 의존성에 의해 정상적으로 초기화되거나 인식될 수 있습니다. 이로 인해 Gradle에서 관련 라이브러리나 설정이 누락된 문제를 해결할 수 있습니다.
  2. spring-boot-starter-aop가 해결한 Gradle 종속성 문제
    Spring Boot 프로젝트에서 여러 종속성이 겹칠 때 문제가 발생할 수 있습니다. spring-boot-starter-aop를 추가하면 Spring의 AOP 관련 클래스들이 다른 필요한 의존성들과 함께 로드됩니다. 예를 들어, AOP가 활성화되면서 AspectJ 라이브러리도 포함되거나, spring-boot-starter-mail과 같은 다른 메일 관련 의존성도 제대로 작동하도록 연결될 수 있습니다.
    즉, AOP 의존성을 추가하면서 Gradle이 자동으로 필요한 다른 라이브러리를 풀에서 받아오고, 메일 발송 기능이 정상적으로 작동하게 되거나, 다른 내부 설정 문제가 해결될 수 있습니다.
  3. Gradle 캐시 문제 해결
    Gradle 캐시나 의존성 충돌 문제로 인해 예상치 못한 에러가 발생할 수 있습니다. spring-boot-starter-aop를 추가하면서 Gradle이 의존성을 재조정하고, 필요한 라이브러리들이 다시 로드되면서 캐시 문제가 해결되었을 수 있습니다.
    이 경우 Gradle 캐시를 삭제하고, 다시 빌드하는 방법으로도 문제를 해결할 수 있습니다. (예: gradle clean)
  4. 스프링의 자동 설정 (Auto Configuration)
    Spring Boot는 자동 설정 기능을 제공하여, 필요한 의존성들이 프로젝트에 추가되면 해당 기능들이 자동으로 설정됩니다. spring-boot-starter-aop 의존성을 추가한 후, 메일 관련 설정이 자동으로 활성화되면서 메일 발송이 제대로 작동했을 가능성이 있습니다. AOP 관련 의존성을 추가하면서 Spring Boot가 자동으로 메일 발송을 위한 설정을 처리했을 수 있습니다.
    결론
    spring-boot-starter-aop 의존성을 추가했을 때 에러가 사라진 이유는, AOP 의존성이 추가되면서 Spring Boot의 설정이나 종속성 문제를 해결했기 때문일 가능성이 큽니다. AOP는 메일 발송과는 직접적인 관계가 없지만, Spring Boot의 자동 설정(auto-configuration) 기능이나 Gradle 의존성 관리에서 필요했던 다른 라이브러리들이 정상적으로 로드되었을 수 있습니다.

따라서, 문제가 해결된 이유는 AOP 관련 의존성이 프로젝트 내 다른 필요한 설정을 올바르게 활성화했기 때문일 것입니다. AOP가 실제로 코드에서 사용되지 않더라도, 관련 의존성을 추가하는 것만으로도 종속성 문제가 해결된 경우일 수 있습니다.

0개의 댓글