본문 바로가기
BE 프로젝트 일대기

[Be] 비밀번호 재설정

by 개발바닥. 2023. 3. 13.

안녕하세요.

이번 글에서는 Gnims 서비스의 비밀번호 재설정을 할 때 서버에서 어떻게 동작하는지 소개해볼려고 합니다.

 

목차

  1. 비밀번호 재설정 흐름
  2. 코드 생성 및 이메일 발송
  3. 코드 복붙하기
  4. 코드 체크
  5. 비밀번호 재설정
  6. 추가로 고려한 사안
  7. 트러블 슈팅
  8. 마무리 (개선할점 등)

 

1. 비밀번호 재설정 흐름

  • 먼저 비밀번호 재설정 흐름을 소개할려고 합니다.

Api 명세
비밀번호 재설정 흐름도

1) 이메일 날리는 api 호출
2) 코드 생성 및 이메일 발송
  2.5) 사용자가 인증 코드를 복붙
3) 인증 코드 확인하는 api 호출
  3.5) 비밀번호 재설정하는 페이지로 넘어감
4) 비밀번호 재설정하는 api 호출

 

2. 코드 생성 및 이메일 발송

  • 먼저 비밀번호 재설정 버튼을 누르면 뜨는 화면입니다.

인증번호 입력 화면

  • 인증 요청 버튼 클릭 시 해당 api가 호출 됩니다.

api

  • 이제 해당 api가 호출될 시 서버에서 돌아가는 로직을 알아보겠습니다.메일을 만들기 전에 필요한 파일을 build.gradle에 작성해 둡니다.

이메일 검증

implementation 'org.springframework.boot:spring-boot-starter-mail'
  • 먼저 해당 이메일이 DB에 존재하는 이메일인지 검증한 후 createEmailValidation 메서드를 호출합니다.

이메일 생성 함수

  • 함수가 실행되면 제일 먼저 인증 코드를 생성합니다.
    (UUID를 이용하여 랜덤 12자리의 코드를 생성해주었습니다.)
  • 그 후 createMessage 함수를 실행하여 메일에 들어갈 내용을 생성해줍니다.

메일 내용

  • 인증 객체를 DB에 저장한 후 이메일을 발송해 주었고,
    try ~ catch 문을 이용하여 이메일 발송 시에 문제가 생겼을 경우에 에러를 처리해주었습니다.
    (인증 객체에는 이메일과 코드가 저장됩니다. ← 인증 코드를 확인할 때 사용)

 

3. 코드 복붙하기

  • 이메일 발송을 마친 후 에러가 없이 잘 실행이 되었다면 해당 메일이 메일함에 수신되었을 것 입니다.

인증 메일

  • 해당 인증 코드를 복사해서 붙여넣으시면 됩니다.

코드 넣기

 

4. 코드 체크

  • 3. 에서 확인 버튼을 클릭 시 해당 api가 호출 됩니다.

api

  • 이때 메서드 타입이 왜 PATCH 인지 의아해 하실 수도 있습니다.
    그 이유는...

이메일 DB

DB를 보시면 아시겠지만 '인증여부'라는 컬럼이 있습니다. (기본 값 == false)
'인증여부'를 false → true로 바꿔주는 것을 통하여 이메일 인증이 되는 것이고 이렇게 컬럼 값을 바꿔주는 것이기 때문에 PATCH가 적절하다고 판단되어 사용하게 되었습니다.

코드 검증

  • 먼저 이메일이 존재하지 않을 시 예외를 던집니다.
  • 그리고 인증 코드가 일치하지 않을 시 예외를 던집니다.
  • 마지막으로 인증 코드는 맞지만, 코드 발송 후 3분이 지나서 유효하지 않을 시 예외를 던집니다.

 

  • 이 모든 과정을 통과할 시 인증 상태를 true로 바꿔줌으로써 인증을 완료합니다.

 

5. 비밀번호 재설정

  • 이메일 인증이 완료되면 그님스 서비스에서 다음과 같은 화면으로 이동합니다.

비밀번호 재설정 화면

  • 확인 버튼 클릭 시 해당 api가 호출 됩니다.

api

  • 여기서 왜 비밀번호 찾기가 아니라 재설정으로 했는지 의문을 가지실 수도 있습니다.

    그런데 최근 웹을 생각해 볼 때, 비밀번호 찾기 대신 재설정을 하는 것을 알 수 있습니다.

    그 이유는 저희 서비스를 기준으로 설명하면 비밀번호를 DB에 저장할 때 단방향으로 암호화해서 저장하기 때문입니다. 단방향으로 암호화해서 넣기 때문에 저희는 비밀번호를 알아내는 것이 사실상 불가능하여 찾기가 아닌 재설정을 합니다.

비밀번호 재설정

먼저 DB에 해당 이메일이 존재하는지 검증하고 해당 이메일의 유저 정보를 찾아옵니다.

  • DB에 해당 이메일의 인증 객체가 존재하는지 검증합니다.
  • 인증 객체가 있다면 인증 객체의 인증 여부가 true인지 검증합니다.
  • 인증 후 3시간이 지났는 지를 확인합니다. (저희는 인증 후 3시간 내에 재설정 하도록 시간을 두었습니다.)


  • 이 모든 과정을 통과하면, 비밀번호를 변경하고 인증 객체를 삭제합니다.

 

위 과정을 끝으로 비밀번호 재설정이 완료되었습니다.

 

6. 추가로 고려한 사안

  • 만약 어떤 사용자가, 이메일 인증하기만 누르고 비밀번호를 안 바꾸면 어떻게 될까요?

    아마 DB에 필요도 없는 인증 객체가 쌓일 것 입니다.

    물론 '이메일 인증만 하고 비밀번호를 안 바꾸어서 쌓이는 인증 객체가 얼마나 많을까?'라고 생각할 수도 있지만 설령 갯수가 적다고 하더라도 전혀 필요없는 정보가 DB에 저장되어 있는 것이기 때문에 삭제해줄 필요가 있다고 생각하였습니다.

인증 메일 DB 비우기

  • 필요없는 인증 메일이 쌓이는 것을 방지하기 위해 매일 00시에 비우도록 스케쥴러를 작성해 보았습니다.

    00시에 비밀번호를 재설정하고 있는 사용자가 있을 수도 있기 때문에 00시 기준 3시간 3분 이상 지난 인증 메일만 삭제하도록 하였습니다.
    (3시간 3분인 이유는 저희 로직 상에서 인증 메일을 최대로 사용하는 시간이 3시간 3분이기 때문입니다.)




  • 추가로 저희 로직을 보면 api 마다 모두 이메일을 검증하는 것을 알 수 있습니다. 이러한 이유는 만약 postman 등의 웹이 아닌 비정상 접근 시 중간 과정을 생략하고 타인의 비밀번호를 바꿀 수도 있다고 판단되었고, 만약 타인의 비밀번호를 바꿀 경우 타인의 계정으로 로그인할 수 있게 되어서 그것을 방지하기 위해 api 마다 이메일을 검증하고, 인증 객체를 제일 마지막에 삭제하는 이유도 마지막까지 인증 상태를 확인하기 위해서 입니다.

 

 

7. 트러블 슈팅

  • 트랙잭션 처리

삭제안되는 코드

  • 상황 : if 문이 실행이 되어 예외를 발생하였지만 delete 문이 실행이 안 되었습니다.


  • 원인 : 'IllegalArgumentException' 의 상위 예외인 'RuntimeException' 은 'Unchecked Exception'이기 때문에 이 예외가 실행될 경우 트랜잭션은 모두 롤백했습니다. 따라서 delete 문이 실행이 안되는 결과가 나왔습니다.


  • 해결 사항: 1. Transactional에 noRollbackFor 달기

트랙잭션에 noRollbackFor


                         2. Checked Exception 던져주기

Unchecked Exception 던지기



  • 선택 : Checked Exception 던지기

    먼저 IllegalArgumentException을 원래 사용했었는데 이것을 'noRollbackFor'을 달아버릴 경우 후에 다른 곳에 영향을 미칠 수도 있을 것이라 생각하여 Tracsional에 'noRollbbackFor'을 다는 것이 아닌 'Unchecked Exception'을 던지는 것을 선택하였습니다.

    그리고 코드를 보면 AuthenticationCodeValidityException이 새로 생긴 것을 알 수 있습니다.

새로운 예외

  • 'Checked Exception'을 던지는 것 뿐이면 기존에 있는 'IOExceptoin' 등의 다른 예외를 던져도 되지만
    이 예외가 발생한 원인은 인증 후 시간이 지나서 코드가 유효하지 않았을 때 발생하기 때문에 의미가 안 맞다고 생각하여 새로운 예외 ('AuthenticalCodeValidityException')를 만들어서 던져 주었습니다.


  • 결과

Unchecked Exception 던지기

  • 해당 코드로 삭제가 잘 되는 것을 확인할 수 있었습니다.

    트랜잭션의 원자성에 대해서 새로 공부해 볼 수 있었고 'Checked Exception', 'Unchecked Exception'에 따라 롤백을 다르게 처리한다는 사실을 알 수 있었습니다. 이것을 처리하기 위해서 필요에 따라 다른 예외를 던지거나 'Transactional'에 'noRollbackFor' 등의 작업을 해줄 수 있다는 것을 알 수 있었습니다.

8. 마무리

  • 비밀번호 재설정을 구현하면서 이메일 인증을 사용해 볼 수 있었고 이를 이용한 로직을
    만들 수 있는 기회가 되었습니다.
    지금은 '인증 코드'를 이용하여 사용자가 코드를 복붙하는 방법을 사용하고 있습니다.
    초기 기획에서 이 방법을 사용하기로 했기 때문에 이 방법으로 구현했는데, 코드를 만들다 보면서 인증 링크를 주어서 이 링크를 통해 링크를 클릭 시 바로 인증이 되고 Gnims 화면으로 넘어가도록 구현할 수도 있었을 것 같습니다.

    '인증 코드'와 '인증 링크' 방식 중 고르자면 저는 '인증 링크'를 사용하는 방식이 더 좋을 것 같다고 생각하였습니다.
    이유는 저희는 웹앱으로 만들긴 했지만 주로 앱 사용자를 고려하여 만들었고, 앱 사용자 입장에서 코드를 복붙하는 과정이 매우 불편할 것이라고 생각했기 때문입니다.

    따라서 후에 기회가 된다면 '인증 코드'에서 '인증 링크'로 바꾸어 바로 Gnims 서비스와 연결되게 만들어 보고 싶습니다.

 

 

 

읽어주셔서 감사합니다.