많은 서비스에서 회원가입 과정에 이제는 당연하다고 여겨지는 이메일 인증 기능을 구현해보려고 합니다.
구현에 앞서 프로젝트에 gradle 설정부터 진행하며 시작해 보겠습니다.
build.gradle 설정
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.mariadb.jdbc:mariadb-java-client'
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.30'
annotationProcessor('org.projectlombok:lombok')
...
}
- 구현하는 과정에 있어 DB는 MariaDB를, 코드의 가독성을 높이기 위해 lombok을 추가적으로 사용했습니다.
application.yml 설정
spring:
mail:
host: smtp.gmail.com
port: 587
username: ${GOOGLE_EMAIL}
password: ${GOOGLE_APP_PASSWORD}
properties:
mail:
smtp:
starttls:
enable: true
required: true
auth: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: org.mariadb.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MariaDBDialect
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
logging:
level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace
이메일 인증을 구현할 때 SMTP를 사용하기 때문에, SMTP의 설정과 테스트를 위해 DB설정도 추가로 진행했습니다.
SMTP 설정
application.yml파일에서 smtp설정 과정에 사용되는 username과 password가 무엇인지에 대해서 설명드리려고 합니다.
username은 말 그대로, 이메일 인증 메일을 보낼 때, 발신자로 보내는 사용자가 되겠습니다.
password는 그럼 해당 발신자 메일 계정의 비밀번호겠네요? 절대 아닙니다.
password는 사용하려는 메일 서비스에 따라 해당 서비스에서 제공하는 "앱 비밀번호"를 사용해야 합니다.
SMTP 앱 비밀번호 생성하기
저는 Gmail을 사용할 예정이기에, 구글 설정으로 가서 앱 비밀번호를 생성하겠습니다.
1. 2단계 인증 설정하기
일단 앱 비밀번호를 생성하기 전에, 2단계 인증을 활성화를 시켜줘야 합니다.
2. 앱 비밀번호 생성하기
이렇게 App name을 입력하고 create을 누르게 되면 아래와 같이 16자리의 비밀번호를 생성해 줍니다.
해당 비밀번호를 application.yml에서 password부분에 입력하시면 됩니다.
이메일 인증 구현
Member Entity
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
private String email;
private String password;
private String role;
private Boolean active;
}
이메일 인증에 사용할 회원 테이블을 일단 생성하기 위해서 Member Entity를 다음과 같이 구성했습니다.
회원 권한과, 계정 활성화 상태도 추가로 관리해 주기 위한 가정을 세워 role, active도 같이 활용할 예정입니다!
MemberSignupReq
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class MemberSignupReq {
private String username;
private String password;
}
회원가입 정보를 받을 Request객체를 다음과 같이 구성했습니다.
MemberController
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final EmailVerifyService emailVerifyService;
@RequestMapping(method = RequestMethod.POST, value = "/signup")
public ResponseEntity<String> signup(@RequestBody MemberSignupReq req) {
// UUID를 생성해서 메일로 전송하고 UUID 반환
String uuid = memberService.sendEmail(req.getUsername());
// 회원 정보 일단 저장
memberService.signup(req.getUsername(), req.getPassword());
// UUID를 DB에 저장
emailVerifyService.save(req.getUsername(), uuid);
return ResponseEntity.ok("성공");
}
}
MemberController에서는 일단 회원이 입력한 정보를 받아서 회원가입을 진행합니다.
크게 3단계로 나눌 수 있는데, 1) 이메일 전송과 함께 UUID 생성, 2) 회원 ID로 사용되는 username과 UUID로 회원 정보 저장, 3) 해당 UUID를 저장 이 다음 위에 보이는 3단계입니다.
여기서 UUID는 Universally Unique Identifier의 약자로, 전 세계적으로 고유한 식별자를 생성하기 위한 표준값입니다.
주로 시스템에서 객체나 Entity를 고유하게 식별하기 위해 사용됩니다.
MemberService
import lombok.RequiredArgsConstructor;
import org.example.day8_email.member.model.Member;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class MemberService {
private final JavaMailSender emailSender;
private final MemberRepository memberRepository;
public String sendEmail(String username) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(username);
message.setSubject("[내 사이트] 가입 환영");
String uuid = UUID.randomUUID().toString();
message.setText("http://localhost:8080/member/verify?email="+username+"&uuid="+uuid);
emailSender.send(message);
return uuid;
}
public void signup(String username, String password) {
Member member = Member.builder()
.email(username)
.password(password)
.role("ROLE_USER")
.active(false)
.build();
memberRepository.save(member);
}
}
Controller에서 활용할 method들을 Service에서 다음과 같이 구현했습니다.
sendEmail에서 일단 backend로만 테스트를 진행할 예정이기에, setText부분(실제 수신 메일 내용)에 가입한 사용자의 이메일과, 생성된 uuid를 넣어서 다시 member/verify로 보내게 로직을 작성하였습니다.
signup method는 builder를 활용하여, 가입을 요청한 사용자의 정보를 일단 DB에 저장하는 과정입니다.
인증이 이루어졌을 시, 저장하는 과정에서 default값으로 넣어준 active(false) 부분을 true로 바꿔주는 부분을 추후에 구현해 보겠습니다.
MemberRepository
import org.example.day8_email.member.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
Repository는 다음과 같이 구현하였습니다.
EmailVerify Entity
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class EmailVerify {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String uuid;
}
이메일 인증 과정에서 활용할 EmailVerify Entity입니다.
EmailVerifyService
import lombok.RequiredArgsConstructor;
import org.example.day8_email.member.model.EmailVerify;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class EmailVerifyService {
private final EmailVerifyRepository emailVerifyRepository;
public Boolean isExist(String email, String uuid) {
Optional<EmailVerify> result = emailVerifyRepository.findByEmail(email);
if(result.isPresent()) {
EmailVerify emailVerify = result.get();
if(emailVerify.getUuid().equals(uuid)) {
return true;
}
}
return false;
}
public void save(String email, String uuid) {
EmailVerify emailVerify = EmailVerify.builder()
.email(email)
.uuid(uuid)
.build();
emailVerifyRepository.save(emailVerify);
}
}
MemberController에서 사용한 save method를 EmailVerifyService에서 구현했습니다.
사용자 이메일과 생성된 UUID를 email_verify 테이블에 저장하는 과정입니다.
isExist method는 추후에 MemberController에서 이미 존재하는 사용자인지 확인하는 과정에서 사용될 예정입니다.
EmailVerifyRepository
import org.example.day8_email.member.model.EmailVerify;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface EmailVerifyRepository extends JpaRepository<EmailVerify, Long> {
Optional<EmailVerify> findByEmail(String email);
}
Repository는 다음과 같이 구현하였습니다.
MemberController verify 추가
@RestController
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
...
@RequestMapping(method = RequestMethod.GET, value = "/verify")
public ResponseEntity<String> verify(String email, String uuid) {
if(emailVerifyService.isExist(email, uuid)) {
memberService.activeMember(email);
return ResponseEntity.ok("이메일 인증 완료");
} else {
return ResponseEntity.ok("이메일 인증이 올바르지 않습니다.");
}
}
}
사용자의 정보를 저장만 하는 게 아닌, 실제 인증 로직이 제대로 돌아갔는지 확인하기 위한, verify 로직도 추가로 구현했습니다.
MemberService activeMember() 추가
@Service
@RequiredArgsConstructor
public class MemberService {
...
public void activeMember(String username) {
Optional<Member> result = memberRepository.findByEmail(username);
if(result.isPresent()) {
memberRepository.save(Member.builder()
.idx(result.get().getIdx())
.email(result.get().getEmail())
.password(result.get().getPassword())
.role(result.get().getRole())
.active(true)
.build());
}
}
}
MemberController에서 사용한 activeMember method는 다음과 같이 구현했습니다.
만약 인증을 진행한 회원이라면, 기존에 active값을 false로 일단 DB에 저장한 부분을 true로 바꿔주는 부분입니다.
결과 확인
이처럼 SMTP를 활용하여 간단하게 이메일 인증 시스템을 만들 수 있습니다.
이번에 구현한 로직은 비록 간단한 로직이지만, 프론트까지 접목시킨다면, 나름 괜찮은 서비스를 만들어 나가는데 도움이 될 것 같습니다.
아래 결과들은 위 로직들에 대한 결과들입니다.
1. 회원가입 결과
2. 인증 이메일 확인
3. 링크 클릭으로 계정 active 활성화
'Spring' 카테고리의 다른 글
Spring Batch (0) | 2024.09.22 |
---|---|
Spring Batch - Scheduler (1) | 2024.09.16 |
Spring Batch란? (1) | 2024.09.09 |
Spring Boot Profile 설정 (0) | 2024.07.22 |
Spring 카카오 로그인 구현하기(Spring Security + OAuth2 + JWT) (0) | 2024.07.08 |