회원가입 기능의 기술적 구성 요소 - Hot-stock/backend GitHub Wiki
1. 이메일 인증 요청 (POST /verify-email)
- 토큰 생성 방식:
서버는 4자리 OTP를 생성하여 사용자의 이메일로 전송합니다. 이메일 전송과 OTP 생성이 동기적으로 처리될 경우, 메인 프로세스가 블로킹되어 전체 요청 처리가 지연될 수 있습니다. 이를 해결하기 위해 이메일 전송 로직은 비동기적 이벤트 처리 방식으로 분리되며, 이를 통해 메인 프로세스는 중단되지 않고 계속 처리될 수 있습니다.flowchart TD A[사용자: 이메일 입력] --> B[POST /verify-email] B --> C[서버: 4자리 OTP 생성] C --> |비동기 처리| D[이벤트 발생: 이메일 전송 요청] D --> E[이메일 서버: 이메일 발송] C --> F[응답: 200 OK]
- 컴포넌트 간 결합도 감소:
비동기 이벤트 처리를 사용함으로써 이메일 전송 기능과 메인 서비스 로직 사이의 결합도가 줄어듭니다.- 유연성 향상: 이메일 전송 실패나 지연이 발생하더라도 메인 프로세스는 영향을 받지 않으며, 이를 통해 서비스의 유연성과 확장성이 높아집니다.
- 확장 가능성: 이메일 외에도 SMS 전송, 로그 기록 등의 비동기 작업을 쉽게 추가할 수 있습니다.
- 분산 처리: 각 작업이 독립적으로 처리되므로, 이메일 서버가 일시적으로 과부하되거나 장애가 발생해도 전체 시스템은 중단되지 않습니다.
- 이메일 전송 실패 처리:
전송이 실패할 경우 재시도 전략을 적용할 수 있습니다. 사용자는 알림을 통해 전송 실패를 통보받고, 일정 시간이 지나면 재시도할 수 있습니다. - 로그 및 모니터링 시스템:
이메일 전송 실패 로그는 **프로메테우스(Prometheus)**와 연동하여 모니터링하며, 알림 시스템(Slack, PagerDuty) 등을 통해 지속적인 오류 모니터링과 빠른 대응이 가능합니다. - 토큰 저장 방식:
생성된 OTP는 **레디스(Redis)**에 저장됩니다. Redis는 메모리 기반으로 매우 빠른 읽기/쓰기 성능을 제공하며, OTP는 일정 시간이 지나면 자동 삭제되도록 **TTL(Time-to-Live)**을 설정할 수 있습니다.- 장점: Redis는 매우 빠른 성능을 제공하며, OTP가 영구적으로 저장될 필요가 없기 때문에 적합한 선택입니다.
- 단점: 메모리 기반이므로 유실 가능성이 있지만, 유실 시 사용자가 재요청을 통해 쉽게 복구할 수 있습니다.
2. 토큰 인증 (POST /verify-token)
- 토큰 검증 방식:
/verify-email
요청 시, 서버는 **"USER:REGISTER:EMAIL"**이라는 형식의 Redis 키를 사용하여 OTP 토큰을 저장합니다. 사용자가/verify-token
요청을 보내면, 저장된 토큰과 입력된 토큰을 비교하여 검증합니다.- 키 네임스페이스 구조 사용 이유:
USER:REGISTER:EMAIL
과 같은 형식의 키는 네임스페이스 구분을 위해 사용됩니다. Redis는 많은 키가 있을 때 이러한 네임스페이스 구조를 사용함으로써 키 관리가 더 효율적이고 명확해지며, 특정 키 그룹을 쉽게 찾을 수 있습니다. - 토큰 저장 이유: 사용자마다 고유한 이메일 주소를 기반으로 토큰을 저장하므로, 사용자가 잘못된 이메일을 입력하더라도 이를 명확히 구분하고 안전하게 검증할 수 있습니다.
- 키 네임스페이스 구조 사용 이유:
- 검증 후 처리:
성공적으로 검증된 토큰은 **"USER:VERIFY:EMAIL"**이라는 형식으로 저장되며, 해당 키 역시 TTL이 설정됩니다. 이는 메모리 낭비 방지를 위한 조치입니다. 사용자는 제한된 시간 내에만 검증된 상태로 유지됩니다.
3. 회원가입 완료 (POST /register)
- 사용자 검증 방식:
회원가입 시, 서버는 "USER:VERIFY:EMAIL" 키를 확인하여 사용자가 검증된 이메일을 통해 회원가입 요청을 하는지 확인합니다.- 검증하는 이유: 이메일 인증이 완료되지 않은 사용자가 악의적으로 회원가입을 시도하는 것을 방지하기 위함입니다. 검증되지 않은 사용자가 임의로 회원가입을 시도할 수 없도록 보안이 강화됩니다.
4. 동시성 처리
- 레디스에서 동시성 처리 요청이 동시에 발생하더라도 마지막 요청이 최종적으로 저장되는 라스트 라이터 윈즈(Last Writer Wins, LWW)방식을 사용합니다.
- 데이터베이스에서 동시성 처리
- 회원가입에서의 동시성 처리: 회원가입에서는 이메일 필드를 유니크(Unique) 필드로 설정하여, 동일한 이메일로 여러 번 회원가입 시도하는 경우를 방지하고 중복 요청을 거절할 수 있습니다.
- 최소한 한 번 실행 보장: 멱등성으로 인해 이메일 필드가 유니크로 설정되었으므로, 동일한 이메일로 여러 번 회원가입 요청이 와도 한 번만 처리됩니다.
5. SHA-256을 선택한 이유
-
단방향 함수:
SHA-256은 단방향 해시 함수로, 입력값을 일정한 규칙에 따라 고정된 크기의 해시 값으로 변환합니다. 이때, 입력값을 다시 원래 값으로 되돌리는 것은 매우 어렵거나 사실상 불가능합니다. 그 이유는 해시 함수가 설계된 방식 때문입니다.- 손실 압축: SHA-256은 입력값의 크기에 상관없이 항상 256비트(32바이트)의 고정된 길이의 출력을 생성합니다. 긴 문자열을 256비트로 압축하는 과정에서 원래 정보의 일부가 손실되기 때문에, 이 해시 값만으로는 원래의 입력값을 복원할 수 없습니다.
SHA-256이란
- 비가역성: 해시 함수는 단방향성이 있어, 해시 값을 기반으로 입력값을 되찾기 위한 공식적인 방법이 없습니다. 이는 수학적으로 설계된 알고리즘의 특징으로, 해시 값이 계산되는 방식이 단순한 역산으로 풀리도록 만들어지지 않았기 때문입니다. **SHA-256(Secure Hash Algorithm 256-bit)**은 SHA-2 계열의 해시 알고리즘 중 하나로, 입력된 데이터를 고정된 크기인 256비트(32바이트)의 해시 값으로 변환하는 단방향 해시 함수입니다. SHA-256은 다양한 보안 애플리케이션에서 데이터 무결성을 확인하는 데 주로 사용되며, 암호화된 비밀번호 저장, 디지털 서명, 블록체인에서 중요한 역할을 합니다.
- 충돌 회피 설계:
SHA-256은 해시 충돌(서로 다른 입력값이 같은 해시 값을 갖는 것)을 매우 낮은 확률로 발생하게 설계되었습니다. 입력값의 미세한 변화가 출력값에 큰 변화를 일으키기 때문에, 특정 해시 값을 가진 입력값을 찾는 것이 매우 어렵습니다. 이로 인해 원본 데이터를 추정하거나 복원하는 것은 사실상 불가능합니다.
작동 방식
-
패딩: 입력 데이터는 512비트의 배수로 만들어져야 합니다. 이를 위해, 데이터 끝에 '1'비트를 추가하고, 그 뒤에 '0'비트를 채워 512비트로 맞춥니다. 마지막 64비트는 원래 데이터의 비트 길이를 저장합니다.
-
초기 값 설정: SHA-256은 고정된 초기 해시 값 8개를 사용합니다. 이 값들은 알고리즘에 의해 미리 정의되어 있습니다.
-
블록 처리: 입력 데이터는 512비트 블록 단위로 처리됩니다. 각 블록은 64번의 반복 연산을 거쳐 해시 값이 계산됩니다.
-
최종 해시 생성: 모든 블록이 처리되면, 최종적으로 256비트(32바이트) 크기의 해시 값이 생성됩니다. 이 값은 원래 데이터가 변경되지 않았음을 검증하는 데 사용됩니다.
결론: SHA-256은 입력 데이터를 패딩하고, 512비트 단위로 나눈 후 64번의 반복 계산을 통해 256비트 크기의 고유한 해시 값을 생성하는 단방향 해시 함수입니다.
특징
SHA-256이 단방향 해시 함수라고 하는 이유는, 입력값으로부터 고정된 크기의 해시 값을 생성할 수 있지만, 생성된 해시 값으로부터 원래의 입력값을 절대로 복원할 수 없기 때문입니다. 이는 역방향 계산이 불가능하기 때문에 단방향 함수로 불립니다.
단방향 해시 함수가 중요한 이유는 비밀번호 , 충돌 저항성이 높기 때문입니다.
-
비밀번호 보안: 단방향 특성 때문에 데이터베이스가 탈취당하더라도 사용자의 평문 입력값은 복호화할 수 없습니다.
-
무결성 보장: 서로 다른 두 입력이 같은 해시 값을 가질 확률이 매우 낮습니다. 이로 인해 입력 데이터의 무결성을 보장할 수 있으며, 데이터가 변조되지 않았음을 확인하는 데 유용합니다.
-
충돌 저항성: 10^256이라는 많은 경우의 수와 더불어, 라운드 연산과 비트 셔플링 같은 복잡한 변환 과정을 통해 서로 다른 입력값이 같은 해시 값을 생성하지 않도록 방지합니다.
- 충돌: 다른 입력 값을 가지고 같은 해시 값을 생성하는 것을 충돌이라고 합니다.
한계점
동일한 입력 값에서는 동일한 해시 값을 같게 됩니다. 해커들이 이를 이용해 레인보우 테이블을 구성해 복호화를 시도할 수 있습니다. 이를 극복하기 위해 Salt 방식이 사용됩니다.
Salt
Salt는 사용자의 입력값에 임의의 랜덤 문자열을 추가한 후 해싱을 수행하는 방식입니다. 이 방법을 사용하면 동일한 입력값이라도 각 사용자마다 다른 해시 값이 생성됩니다. Salt 값이 유출되더라도 보안에는 큰 문제가 없습니다. 그 이유는 각 Salt 값에 대해 별도의 테이블을 만들어야 하기 때문에 해킹이 거의 불가능해집니다.