Spring

Spring Boot 로 이메일 회원가입 하기

728x90

토이 프로젝트를 하다가 회원가입과 로그인, 로그아웃 기능을 만들게 되었는데

그때 사용했던 개념들을 정리하고자 한다

 

먼저 이메일을 통해서 회원가입을 했던 과정을 정리한다

 

- 개발환경

spring boot : 2.5.2

java : 16

maven : 3.6.3

 

 

- 의존성

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
cs

thymeleaf 는 이메일 보낼때 적을 내용을 담고 있다

(java 에서 로직처리한 데이터를 전달받아 랜더링하기 위해서 사용됨)

 

 

 

- Gmail 설정하기

: 실제 서비스 운영시에는 Gmail 이 아닌 다른 메일 서버를 사용하겠지만 (보내는 양에 한도가 있기 때문) 

여기선 Gmail 을 썼다

그래서 https://www.siteground.com/kb/gmail-smtp-server/ 에 적힌 내용을 기반으로 application.properties 를 다음과 같이 작성했다

 

application.properties

spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=<개인-gmail-id>
spring.mail.password=<개인-gmail-password>
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
cs

 

 

* Spring Boot 에서 Mail 을 보내기 위한 사전지식

: Spring Boot 를 통해서 이메일을 보내기 위해 Spring 에서 제공하는 인터페이스와 클래스들에 대해서 어느정도 사전지식이 필요하다

기본적으로 제공되는 인터페이스 및 클래스들은 다음과 같다

 

1) MailSender 인터페이스

: 단순한 이메일을 보내기위한 기본 기능들을 제공하는 최상위 레벨의 인터페이스다

 

2) JavaMailSender 인터페이스

: MailSender 인터페이스의 하위 인터페이스 이며, 이 인터페이스는 MIME 메시지를 지원하고 대체로 MimeMessageHelper 클래스와 같이 쓰인다 (아래 서술할 MimeMessagePreparator 와 같이 쓰임)

 

3) JavaMailSenderImpl 클래스

: 이름에서 알 수 있듯이 JavaMailSender 인터페이스의 구현 클래스이다.

 

4) SimpleMailMessage 클래스

: 단순한 메일 보내는데 쓰인다 (단순 텍스트만 보낼때 사용)

 

5) MimeMessagePreparator 인터페이스

: MIME 메시지 준비를 위해서 필요한 콜백 인터페이스를 제공한다

 

6) MimeMessageHelper 클래스

: MIME 메시지 생성을 위해 쓰이는 클래스다

이미지, 파일, 텍스트 등을 html 형식으로 제공한다

 

* MIME 이란 단순 텍스트 뿐 아니라 다른 여러 바이너리 파일을 메일에 첨부할때 쓰인다.

 

 

 

* 이메일로 회원가입하는 코드들

0) SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**")
                .permitAll()
                .anyRequest()
                .authenticated();
    }
 
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
cs

 

 

 

1) Controller

@RestController
@RequestMapping("/api/auth")
@AllArgsConstructor
public class AuthController {
 
    private final AuthService authService;
 
    @PostMapping("/signup")
    public ResponseEntity<String> signup(@RequestBody RegisterRequest registerRequest) {
        authService.signup(registerRequest);
        return new ResponseEntity<>("회원가입 완료", HttpStatus.OK);
    }
 
    @GetMapping("accountVerification/{token}")
    public ResponseEntity<String> verifyAccount(@PathVariable String token) {
        authService.verifyAccount(token);
        return new ResponseEntity<>("계정 활성화가 성공적으로 되었습니다.", HttpStatus.OK);
    }
}
cs

RegisterRequest 는 유저로 부터 이메일과 비밀번호를 받는 DTO 이다

이메일을 받은 유저가 링크를 받았을때 그 링크를 클릭하면 그 때 활성화된 계정으로 바뀌도록 

verifyAccount 라는 메소드를 추가했다.

 

 

2) Service

2-1. AuthService.java

@Service
@Slf4j
@AllArgsConstructor
public class AuthService {
 
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final VerificationTokenRepository verificationTokenRepository;
    private final MailContentBuilder mailContentBuilder;
    private final MailService mailService;
 
    @Transactional
    public void signup(RegisterRequest registerRequest) {
        User user = new User();
        user.setEmail(registerRequest.getEmail());
        user.setPassword(encodePassword(registerRequest.getPassword()));
        user.setEnabled(false);
 
        userRepository.save(user);
 
        String token = generateVerificationToken(user);
        String link = Constants.ACTIVATION_EMAIL + "/" + token;
        String message = mailContentBuilder.build(link);
 
        mailService.sendMail(new NotificationEmail("계정 활성화를 실행해주세요.", user.getEmail(), message));
    }
 
    private String generateVerificationToken(User user) {
        String token = UUID.randomUUID().toString();
        VerificationToken verificationToken = new VerificationToken();
        verificationToken.setToken(token);
        verificationToken.setUser(user);
        verificationTokenRepository.save(verificationToken);
        return token;
    }
 
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }
 
    public void verifyAccount(String token) {
        Optional<VerificationToken> verificationTokenOptional = verificationTokenRepository.findByToken(token);
        verificationTokenOptional.orElseThrow(() -> new CustomException("잘못된 토큰"));
        fetchUserAndEnable(verificationTokenOptional.get());
    }
 
    @Transactional
    public void fetchUserAndEnable(VerificationToken verificationToken) {
        String email = verificationToken.getUser().getEmail();
        User user = userRepository.findByEmail(email).orElseThrow(() -> new CustomException("유저를 찾을 수 없음 " + email));
        user.setEnabled(true);
        userRepository.save(user);
    }
}
cs

회원가입시에 이메일 받은 유저가 이메일 링크를 클릭해야만 계정 활성화를 하도록 했다.

그래서 signup 메소드에서 먼저 userRepository.save(user) 로 유저를 DB 에 저장한 이후 

이메일에 verificationToken 이라는 검증 토큰을 생성한후 이메일로 보내게 했다.

 

이메일에서 받은 검증 토큰은 UUID 를 통해서 랜덤값을 만들었다.

검증은 verifyAccout 메소드에서 한다.

 

그리고 최종적으로 계정활성화는 fetchUserAndEnable 메소드에서 실행한다.

 

 

2-2. MailService.java

@Service
@Slf4j
@AllArgsConstructor
public class MailService {
 
    private final JavaMailSender mailSender;
 
    @Async
    void sendMail(NotificationEmail notificationEmail) {
        MimeMessagePreparator messagePreparator = mimeMessage -> {
            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true"UTF-8");
            messageHelper.setFrom("보내는 사람 이메일 ");
            messageHelper.setTo(notificationEmail.getRecipient());
            messageHelper.setSubject(notificationEmail.getSubject());
            messageHelper.setText(notificationEmail.getBody(), true);
        };
 
        try {
            mailSender.send(messagePreparator);
            log.info("활성화 메일이 보내졌다");
        } catch (MailException e) {
            log.error(String.valueOf(e));
            throw new CustomException("메일을 여기로 보내는 중 에러 발생 :  " + notificationEmail.getRecipient());
        }
    }
}
 
cs

위에서 언급한 JavaMailSender 와 MimeMessagePreparator, MimeMessageHelper 를 같이 사용하였다.

NotificationEmail 은 이메일 전송시에 담을 제목, 수신자, 이메일 내용 등을 담는 DTO 이다.

 

 

3) 메일 내용

AuthService.java 에 보면 회원가입 메소드에 String message = mailContentBuilder.build(link) 가 있다

이는 앞서 설정한 Thymeleaf 뷰 템플릿을 통해서 이메일에 작성할 html 코드를 쓰기 위함이다

 

MailContentBuilder.java

@Service
@AllArgsConstructor
public class MailContentBuilder {
 
    private final TemplateEngine templateEngine;
 
    String build(String message) {
        Context context = new Context();
        context.setVariable("link", message);
        return templateEngine.process("mailTemplate", context);
    }
}
cs

뷰 템플릿에 지정할 변수 link 를 정의했다.

그리고 뷰 템플릿의 파일 이름은 mailTemplate.html 이다

 

mailTemplate.html

<!DOCTYPE html>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>이메일 인증 링크</title>
    </head>
    <body>
        <span>계정 활성화를 위해서 아래의 인증 링크를 클릭해주세요.</span><br>
        <a th:href="${link}">인증 링크</a>
    </body>
</html>
cs

 

 

이후 메일을 실제로 보내게 되면 다음과 같이 이메일을 받을 수 있게 된다.

 

 

- References

1) Guide to Spring Email : https://www.baeldung.com/spring-email

2) Spring Boot 이메일 전송 : https://victorydntmd.tistory.com/342

728x90

'Spring' 카테고리의 다른 글

Spring Cloud Eureka 와 기본 REST API  (0) 2021.09.14
Spring Boot 와 JWT 로 로그인하기  (0) 2021.07.06
Spring Cloud Netflix Eureka  (0) 2021.07.05
Spring Cloud 와 Microservice 란  (0) 2021.07.01
Swagger 를 통한 REST API 문서화  (0) 2021.04.30