코딩과 결혼합니다

[Game_Crew] 리팩토링 + 트러블슈팅 : 이메일 인증 코드 리팩토링 본문

코딩과 매일매일♥/Game_Crew

[Game_Crew] 리팩토링 + 트러블슈팅 : 이메일 인증 코드 리팩토링

코딩러버 2023. 10. 31. 19:46
728x90

 


📌 [ 이메일 중복확인 API ]와 [ 이메일 인증 코드 발송 API ] 합치기

  • 사용자의 무분별한 계정 등록을 방지하기 위하여 인증 번호를 받을 수 있는 이메일을 입력하도록 유도
  • 이메일을 입력하면 중복체크를 먼저 진행
  • 중복되지 않았다면 "사용할 수 있는 이메일입니다."와 같은 메시지와 인증 code 번호를 프런트에 반환

//이메일 중복확인 및 인증코드 전송
@PostMapping("/signup/email")
public MessageResponseDto checkAndSendEmail(@RequestBody @Valid CheckEmailRequestDto requestDto) {
    String authCode = signupService.checkAndSendEmail(requestDto.getEmail());

    return new MessageResponseDto(Message.AVAILABLE_EMAIL + "인증 코드: " + authCode, HttpStatus.OK);
}
//이메일 중복 확인 및 인증코드 전송
public String checkAndSendEmail(String email) {
    Optional<User> checkEmail = userRepository.findByEmail(email);
    if (checkEmail.isPresent()) {
        throw new CustomException(ErrorMessage.DUPLICATE_EMAIL_EXISTS, HttpStatus.CONFLICT, true);
    }

    // 중복되는 이메일이 없으면 인증 코드 발송
    try{
        return sendSimpleMessage(email);
    }catch(Exception e){
        throw new CustomException(ErrorMessage.FAILED_TO_SEND_EMAIL, HttpStatus.INTERNAL_SERVER_ERROR, true);
    }
}

 

📌 Auth의 EmailControll, EmailService➡️ User 패키지의 SignUpController와 SignUpService에 합침

  • 관련 api 끼리 합침으로 auth domin 삭제(필요시에 다시 만들겠음)
  • LoginRequestDto의 경우 filter 단에서 처리하고 있으므로 global 패키지에 request 하위 패키지 생성 후 그곳으로 옮김

이렇게 함으로써 이전의 URL에 이메일 주소가 그대로 드러나 보안적으로 문제가 있는 점이 해결되었다.

https://coding-s2-chaewon.tistory.com/195

 

[Game_Crew] 트러블슈팅 : 이메일 인증 완성

문제 jakarta.mail.MessagingException: Could not connect to SMTP host: smtp.naver.com, port: 587; 메일 서버 연결에 실패. SMTP 호스트 smtp.naver.com의 587에 연결할 수 없다는 내용이다. javax.net.ssl.SSLException: Unsupported or unre

coding-s2-chaewon.tistory.com

파싱 문제는 RequestDto를 통해 데이터를 받아 사용함으로 해결할 수 있었다.


 

😎트러블 슈팅


문제 이메일 중복확인 후에 받은 인증 코드를 입력했을 때 결괏값으로 계속해서 false가 나왔다.

 

이유 

    @PostMapping("/signup/email/check")
    @ResponseBody
    public boolean verifyCode(@RequestBody CodeRequestDto code){
        boolean isVerified = code.equals(signupService.getEPw());
        return isVerified;
    }

equals() 메서드가 CodeRequestDto 클래스에 정의되어 있지만, 이 메서드는 CodeRequestDto 인스턴스 자체를 비교하고 있다. 즉, code는 CodeRequestDto 객체이고, 이 객체와 문자열인 signupService.getEPw()를 비교하려 하기 때문에 항상 false가 반환되는 것이다.

 

해결

boolean isVerified = code.getCode().equals(signupService.getEPw());

getCode() 메서드를 사용하여 실제 인증 코드 문자열을 가져와 서비스에서 반환된 인증 코드와 비교한다.


😎트러블 슈팅(2)

 

이러한 의문점이 생겼다.

  1. 사용자가 동시에 인증 코드를 받고 입력한 경우 서버는 어떻게 각 사용자의 코드인지 구분할 수 있나?
  2. 여러 요청이 동시에 접근했을 때 이 동시성 문제를 해결하기 위해 어떠한 행동을 취해야 하는가?
  3. 인증 코드와 email 주소를 DB에 저장해 놓고 비교한 뒤에 사용한 인증코드는 어떻게 삭제해야 할까?

해결

    //이메일 중복확인 및 인증코드 전송
    @PostMapping("/signup/email")
    public MessageResponseDto checkAndSendEmail(@RequestBody @Valid CheckEmailRequestDto requestDto) {
        String authCode = signupService.checkAndSendEmail(requestDto.getEmail());
        Auth auth = new Auth(requestDto.getEmail(), authCode);
        authRepository.save(auth);
        return new MessageResponseDto(Message.AVAILABLE_EMAIL + "인증 코드: " + authCode, HttpStatus.OK);
    }

사용할 수 있는 이메일이면 그 이메일 주소와 code를 db에 저장한다.

    @PostMapping("/signup/email/check")
    @ResponseBody
    public MessageResponseDto verifyCode(@RequestBody EmailCodeRequestDto requestDto){
        return signupService.verifyCode(requestDto.getEmail(), requestDto.getCode());
    }

다음 프런트로부터 받은 입력값과 비교하여 이메일과 코드가 일치하는지 확인한다. 이렇게 여러 개의 요청이 동시에 오더라도 구분될 수 있게 구현하였다.

1,2번은 해결하였으나 3번의 경우에는 우선적으로 MVP기능을 모두 구현한 다음에 시도해 볼 것이다!


😎트러블 슈팅(3) - 인증코드가 랜덤값이 아닌 같은 값으로만 발송된다.

Duplicate entry '800182' for key 'auth.UK_nn8juweilrld7bkd9qfwjrvxx'

인증코드가 계속해서 '800182'로만 보내지고 code에 unique를 걸어놨기에 중복된 값을 가지고 접근하려 하여 계속해서 오류가 발생했다. 

    @Column(nullable = false)
    private String code;

먼저 인증코드가 랜덤으로 만들어지기는 하나 혹여나 같은 값을 낼 수 도 있으니 unique 한 값이 아니어도 허용할 수 있게 하였으며, 계속해서 같은 값을 반환하는 문제를 해결하기 위하여 코드의 로직을 뜯어보았다.

public String checkAndSendEmail(String email) {
    //...

    // 중복되는 이메일이 없으면 인증 코드 발송
    try{
        return sendSimpleMessage(email);
    }catch(Exception e){
        throw new CustomException(ErrorMessage.FAILED_TO_SEND_EMAIL, HttpStatus.INTERNAL_SERVER_ERROR, true);
    }
}

public MessageResponseDto verifyCode(String email, String code) {
    //이메일과 코드 일치하는지 체크하는 메서드
}

private final String ePw = createKey();

public MimeMessage createMessage(String to)throws MessagingException, UnsupportedEncodingException {
    log.info("보내는 대상 : "+ to);
    log.info("인증 번호 : " + ePw);
    //메일 양식과 전송관련 설정 메서드
}

public static String createKey() {
    //랜덤한 인증코드를 반환하는 메서드
}

public String sendSimpleMessage(String to)throws Exception {
    //메일 발송 관련 메서드, 메일로 보냈던 인증 코드를 서버로 리턴
    return ePw;
}

이전 코드 같은 경우에는 ePw 변수가 createMessage() 메서드와 sendSimplieMessage() 메서드 사이에 위치해 있어서 같은 코드 값을 반환하는 현상이 발생하였다. ePw 변수를 전역 변수로 선언하고 createKey() 메서드를 통해 한 번만 생성한 후, 이 값을 계속 사용하였기 때문이다.

public MimeMessage createMessage(String to)throws MessagingException, UnsupportedEncodingException {
    log.info("보내는 대상 : "+ to);
    log.info("인증 번호 : " + ePw);
    //메일 양식과 전송관련 설정 메서드
}

public String sendSimpleMessage(String to)throws Exception {
    // 인증코드 생성 이동
    String ePw = createKey();
    MimeMessage message = createMessage(to, ePw);
    try{
        javaMailSender.send(message); // 메일 발송
    }catch(MailException es){
        es.printStackTrace();
        throw new IllegalArgumentException(ErrorMessage.FAILED_TO_SEND_EMAIL);
    }
    return ePw; // 메일로 보냈던 인증 코드를 서버로 리턴
}

기존에 createKey() 메서드를 이용해 인증 코드를 생성하던 것을 sendSimpleMessage 메서드 안으로 이동하고, ePw라는 전역 변수를 제거하여 각 요청마다 새로운 인증코드를 생성하게 하였다.