티스토리 뷰

얼마 전 웹 프로젝트에서 메일을 통한 유저의 이메일 인증 기능을 개발했다. 이메일을 유저 아이디로 사용하기 때문에, 사용자의 이메일이 유효한지 확인하는 절차가 필요했기 때문이다.

사전 지식 및 설정


메일 시스템과 SMTP에 관한 포스팅
https://bool-flower.tistory.com/13

 

이메일과 SMTP

유튜브 한기대박승철교수님 채널의 컴퓨터 네트워크 제9강 이메일과 SMTP 강의를 보며 작성된 글이다. 강의를 들으면서 같이 좋을 것 같다. 최근 진행하던 프로젝트에서 메일을 통한 회원 인증

bool-flower.tistory.com

구글 SMTP 설정방법

https://hyunmin1906.tistory.com/276

 

[Go] Google Gmail SMTP 설정 방법 및 메일 전송

■ SMTP 간이 우편 전송 프로토콜(Simple Mail Transfer Protocol)의 약자. 이메일 전송에 사용되는 네트워크 프로토콜이다. 인터넷에서 메일 전송에 사용되는 표준이다. 1982년 RFC821에서 ..

hyunmin1906.tistory.com

스프링 부트 application.properties 설정 및 의존성 추가

# email
spring.mail.host=smtp.gmail.com
spring.mail.port=465
spring.mail.username=이메일
spring.mail.password=계정 비밀번호 또는 설정된 앱 비밀번호(2단계 인증 계정인 경우)
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.ssl.trust=smtp.gmail.com
spring.mail.properties.mail.smtp.starttls.enable=true
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.6.4'

개발


이메일 인증은 사용자에게 인증 요청 링크를 보내는 방법과 인증 코드를 보내서 입력하게 하는 방법이 있는데, 인증 링크를 보내는 방법을 채택했다. 메일 인증 기능의 구체적인 절차는 아래와 같다.

  • 클라이언트로부터 인증할 이메일 아이디 받기
  • 메일의 인증 링크가 포함된 메일 작성하기
  • 요청받은 이메일로 메일 전송하기
  • 링크 요청을 받으면 인증 완료 처리하기

사용자로부터 인증할 이메일 아이디 받기

@Api(tags = "이메일 인증 처리 API")
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class EmailConfirmController {

    private final EmailConfirmTokenService emailConfirmTokenService;
    private final MailService mailService;

    @GetMapping("/confirm/send")
    public void sendEmailToken(
            @RequestParam @Email 
            String email
    ) {
        UUID tokenId = emailConfirmTokenService.createToken(email).getTokenId();

        mailService.sendConfirmToken(email, tokenId);
    }

    @GetMapping("/confirm")
    public boolean authenticateEmail(
            @RequestParam @NotNull
            UUID tokenId,
            @RequestParam @Email
            String targetEmail
    ) {

        return emailConfirmTokenService.checkToken(tokenId, targetEmail);
    }
}

sendEmailToken 메서드의 파라미터로 이메일을 받도록 구현했다. 그러면 emailConfirmTokenService 객체가 이메일을 확인할 인증 토큰을 생성해 mailService 객체에 전달하고, mailService는 인증 토큰을 해당 이메일로 전송한다.

이후, 사용자가 수신한 메일에서 링크를 클릭하면 authenticateEmail 메서드가 수행되어 토큰 확인이 되도록 구현했다.

EmailConfirmToken 엔터티

@DynamicInsert
@Getter
@NoArgsConstructor
@EqualsAndHashCode
@Entity
public class EmailConfirmToken {

    private static final long EMAIL_TOKEN_EXPIRATION_TIME_VALUE = 5L;

    @Id @Column(length = 16)
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    private UUID tokenId;

    @Column(nullable = false)
    private String targetEmail;

    @Column(nullable = false)
    private LocalDateTime expiredAt;

    @Column(nullable = false)
    @ColumnDefault("false")
    private boolean confirmed;

    public EmailConfirmToken(
            String targetEmail
    ) {

        this.targetEmail = targetEmail;
        this.expiredAt = LocalDateTime.now().plusMinutes(EMAIL_TOKEN_EXPIRATION_TIME_VALUE);
    }

    public void setTokenId(UUID tokenId) {
        this.tokenId = tokenId;
    }

    public boolean isExpired() {

        if (expiredAt.isBefore(LocalDateTime.now())) {
            return true;
        }
        return false;
    }

    public void confirm() {
        this.confirmed = true;
    }
}

UUID를 기본키로 설정했다.

EmailConfirmTokenService

EmailConfirmTokenService 객체는 인증 토큰 생성과 인증 토큰을 확인하는 책임을 가진다.

@Service
@RequiredArgsConstructor
public class EmailConfirmTokenService {

    private final EmailConfirmTokenRepository emailConfirmTokenRepository;
    private final UserRepository userRepository;

    @Transactional
    public EmailConfirmToken createToken(
            String targetEmail
    ) {

        if (emailConfirmTokenRepository.existsByTargetEmail(targetEmail)) {
            emailConfirmTokenRepository.deleteByTargetEmail(targetEmail);
        }

        EmailConfirmToken token = new EmailConfirmToken(targetEmail);
        emailConfirmTokenRepository.save(token);

        return token;
    }

    @Transactional
    public boolean checkToken(
            UUID tokenId,
            String targetEmail
    ) {

        EmailConfirmToken confirmToken = emailConfirmTokenRepository.findByTokenId(tokenId)
                .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 코드입니다."));

        User targetUser = userRepository.findByUserEmail(targetEmail)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));

        if (confirmToken.isExpired()) {
            return false;
        }

        confirmToken.confirm();
        targetUser.enableUser();

        return true;
    }
}

MailService

MailService 객체는 메일을 MailMessage를 생성하여 인증이 필요한 이메일로 전송할 책임을 가지도록 구현했다. 지금은 링크만 보낼 것이기 때문에 SimplMailMessage를 사용했다.

@Service
@RequiredArgsConstructor
public class MailService {

    private static final String DOMAIN_URL = "http://localhost:8080";
    private final JavaMailSender mailSender;

    public void sendConfirmToken(String receiver, UUID tokenId) {

        SimpleMailMessage mailMessage = new SimpleMailMessage();

        mailMessage.setTo(receiver);
        mailMessage.setSubject("가치코딩 이메일 인증");
        mailMessage.setText(DOMAIN_URL +
                "/api/confirm?targetEmail=" + receiver +
                "&tokenId=" + tokenId);

        mailSender.send(mailMessage);
    }
}

 

테스트


스웨거를 통해 정상적으로 동작하는 것을 확인했다.

 

참조


https://gilssang97.tistory.com/60

https://moonong.tistory.com/45

https://javaju.tistory.com/100

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday