[Spring] UserDetails의 enabled를 이용해 가입 인증 기능 구현

rekv·2025년 2월 11일

Spring

목록 보기
13/15

로그인 구현에서 이어지는 실습

가입할 때 가입 메일이 전송되도록 설정

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-mail'

위의 라이브러리를 추가해준다.

application.yaml

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: 구글아이디@gmail.com
    password: 구글에서 발급받은 password
    properties:
      mail:
        smtp:
          starttls:
            enable: true
            required: true
          auth: true
          timeout: 5000 #5초

starttls는 http와 https의 차이와 비슷한 설정으로, 설정하지 않고 넘어가면 메일이 스팸함으로 넘어갈 수도 있다.

패스워드 발급

GMail로 들어가서 설정에 들어간 다음 IMAP 사용을 체크해준다. 지금 설정하는 메일 주소를 통해서 추후 가입 인증 메일이 발송된다.

그다음 구글 계정 관리로 들어가서 보안 > 2단계 인증을 설정한다.

2단계 인증 설정을 완료했으면 상단 검색창에 앱 비밀번호를 검색한 후 비밀번호를 발급받는다. 앱 이름은 아무거나 적어주면 된다.

생성된 비밀번호를 띄어쓰기 없이 application.yaml에 적어주면 기본 설정은 끝난다.

UserService.java

signup() 함수에 sendEmail() 함수를 추가, 가입을 하면 아래와 같은 이메일이 온 것을 볼 수 있다.

이메일의 링크를 클릭하여 메일 인증 기능 추가

User.java

가입할 때 이메일을 통해 인증이 되었는지 아닌지 구분하기 위해 User 엔티티에 enabled를 추가한다.

EmailVerify.java

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class EmailVerify {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long idx;
  @ManyToOne
  @JoinColumn(name="user_idx")
  private User user;
  private String uuid;
}

사용자가 가입을 완료한 후 인증받을 이메일과 서버에서 생성한 토큰을 비교할 수 있도록 EmailVerify 엔티티를 만들어 user와 uuid(인증을 위한 토큰)을 저장한다.

UserController.java

@RequiredArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {
  private final UserService userService;

  @GetMapping("/verify")
  public void verify(String uuid) {
    userService.verify(uuid);
  }
}

UserService.java

@RequiredArgsConstructor
@Service
public class UserService implements UserDetailsService {
  private final UserRepository userRepository;
  private final PasswordEncoder passwordEncoder;
  private final JavaMailSender mailSender;
  private final EmailVerifyRepository emailVerifyRepository;

  public void sendEmail(String uuid, String email){
    SimpleMailMessage message = new SimpleMailMessage();
    message.setTo(email);
    message.setSubject("[Welcome] Hello World!");
    message.setText(
            "http://localhost:8080/user/verify?uuid="+uuid
        );
    mailSender.send(message);
  }

  public UserDto.SignupResponse signup(UserDto.SignupRequest dto) {
    String uuid = UUID.randomUUID().toString();
    User user = userRepository.save(dto.toEntity(passwordEncoder.encode(dto.getPassword())));
    //이메일인증 정보 저장
    emailVerifyRepository.save(EmailVerify.builder().user(user).uuid(uuid).build());
    //이메일 전송
    sendEmail(uuid, dto.getEmail());
    return UserDto.SignupResponse.from(user);
  }
  
    public void verify(String uuid) {
    EmailVerify emailVerify = emailVerifyRepository.findByUuid(uuid).orElseThrow();

    User user = emailVerify.getUser();
    user.userVerify();

    userRepository.save(user);
  }
}

UserService에서 사용자의 메일로 인증을 위한 링크를 보내도록 수정하고, verify() 함수를 추가하여 서버가 생성한 토큰과 사용자가 가입할 때 메일로 받은 토큰값이 일치하는지 비교한다.

이메일로 온 링크를 클릭하면 EmailVerify에 등록된 uuid와 비교 후, 두 토큰 값이 일치하면 User의 enabled가 true로 바뀌면서 인증이 완료된다.

+)

액세스 거부

만약 다음과 같은 화면이 뜬다면?

403 에러는 대개 권한 문제이므로 SecurityFilterChain에서 /verify()를 추가해주는 걸 잊지 말자

생각해볼 것

회원 정보 저장, 이메일 인증 정보 저장, 이메일 전송 각각의 기능에 앞서, 회원가입시 이메일 인증은 어떻게 처리하는 게 좋을까?

1. 기능별 (Controller, Service, Repository 각자 따로)

emailverify 기능
	EmailVerifyController
	EmailVerifyService (이메일 인증 정보 저장, 이메일 전송)
	EmailVerifyRepository
	EmailVerify
	
user기능
	UserController
	UserService (회원 정보 저장)
	UserRepository
	User

1) 컨트롤러가 여러 서비스에 의존

UserController -> EmailVerifyService
UserController -> UserService
장점 : 클라이언트와 서버 통신 줄어듦
단점 : 트랜잭션 처리가 애매해짐

2) 클라이언트가 다른 서비스에 의존

UserController -> UserService -> EmailVerifyService
장점 : 클라이언트와 서버 통신 줄어듦, 트랜잭션 처리가 간단
단점 : 서비스에서 계층이 나눠짐 (의존성 문제가 생길 수 있음)
https://jangjjolkit.tistory.com/62 (서비스에서 서비스 호출)

3) 클라이언트가 서버를 여러 번 호출

UserController -> UserService
EmailVerifyController -> EmailVerifyService
장점 : 기능 별로 완전 분리, SRP 원칙
단점 : 클라이언트와 서버 통신이 많아진다 (성능이 떨어지므로 비추천)

2. 특정 기능에 필요한 파일만 추가

 emailverify 기능
	EmailVerifyRepository
	EmailVerify
	
user기능
	UserController
	UserService (회원 정보 저장, 이메일 인증 정보 저장, 이메일 전송)
	UserRepository
	User

하나의 서비스가 여러 레포지토리에 의존

UserController->UserService

3. 프로젝트 쪼개기

MSA

0개의 댓글