오늘날 디지털 세상에서 온라인 보안은 그 어느 때보다 중요하며, 그 핵심 요소 중 하나가 바로 사용자 인증입니다.
이메일 기반 인증은 사용자 회원가입과 로그인에 가장 널리 쓰이는 방식 중 하나입니다. 하지만 이 방식에는 한 가지 문제점이 있습니다. 사용자가 회원가입 과정에서 가짜 이메일 주소나 잘못된 이메일 주소를 입력할 수 있다는 점입니다. 이는 보안 위협과 사기 행위로 이어질 수 있으며, 바로 이 지점에서 이메일 검증 서비스(Email Validation Service)가 큰 도움이 됩니다.
이 튜토리얼에서는 이메일 검증 서비스를 활용해 회원가입 시 연락처 정보를 검증함으로써 이메일 유효성 확인 과정을 자동화하는 방법을 알아보겠습니다.
해당 API는 이메일 주소의 구문 형식, 도메인, 메일박스를 검사할 뿐만 아니라 일회용(disposable) 이메일이나 위험한 이메일까지 탐지할 수 있습니다.
이 API를 애플리케이션에 통합하면 유효하고 실제 존재하는 이메일 주소만 회원가입에 사용되도록 보장할 수 있으며, 이를 통해 애플리케이션의 보안을 한층 강화할 수 있습니다.
사전 준비 사항
튜토리얼을 시작하기 전에 다음 요구 사항을 충족했는지 확인하세요.
- Python에 대한 실무 수준의 이해
- 시스템에 설치된 Python 3.8 이상
- Flask, Flask Blueprint, Requests 라이브러리에 대한 기본 지식
가상 환경 설정 방법
코딩을 시작하기 전에 필요한 도구와 라이브러리가 모두 준비되어 있는지 확인해야 합니다. 깨끗하고 독립적인 개발 환경을 유지하기 위해 venv를 사용해 가상 환경을 생성하겠습니다.
먼저 프로젝트 디렉터리를 생성하고 터미널에서 해당 디렉터리로 이동합니다.
mkdir email-validation
cd email-validation
다음 명령어로 env라는 이름의 가상 환경을 생성합니다.
python -m venv env
요즘 배포되는 Python에는 가상 환경 생성을 위한 venv 라이브러리가 기본으로 포함되어 있습니다.
가상 환경을 다음과 같이 활성화합니다.
source env/bin/activate
참고: Windows를 사용 중이라면 source env/Scripts/activate 명령어로 환경을 활성화해야 합니다.
터미널 프롬프트에 (env)가 표시되면 가상 환경이 정상적으로 활성화된 것입니다.
이메일 검증은 사용자 인증이 필요한 모든 웹 애플리케이션에서 필수적인 과정이며, 이를 수행하는 방법은 여러 가지가 있습니다.
그중 하나는 emailvalidation.io 같은 이메일 검증 서비스를 활용하는 것입니다. 이 API를 사용하면 개발자가 이메일 주소가 구문적으로 올바른지, 도메인이 실제로 존재하는지, 메일박스가 메시지를 수신할 수 있는지를 확인하여 이메일 주소를 검증할 수 있습니다.
이 API는 다양한 요구에 맞춘 여러 요금제를 제공합니다. 무료 플랜은 최대 100개의 이메일까지 검증할 수 있어 테스트 목적으로는 충분합니다. 유료 플랜은 월 $9.99부터 시작하며, 더 많은 요청 처리량, 추가 기능, 더 빠른 응답 속도를 제공합니다.
Python으로 이메일 검증 함수 작성하기
이 섹션에서는 API 엔드포인트로 GET 요청을 전송하고 검증할 이메일 주소를 파라미터로 전달하는 Python 함수를 작성합니다.
API 요청을 인증하려면 요청과 함께 API 키도 전달해야 합니다. 진행하기 전에 emailvalidation.io에 계정을 만들어 API 키를 발급받아야 합니다. 계정을 생성하면 아래와 비슷한 대시보드로 이동하게 되며, API 키는 검은색으로 강조된 영역에서 확인할 수 있습니다.
GET 요청을 보내려면 가상 환경에 requests 라이브러리를 설치해야 합니다.
pip install requests
다음으로 test.py 파일을 생성하고 아래 코드를 추가합니다.
import requests
from requests.structures import CaseInsensitiveDict
def is_valid(email: str):
url = f"https://api.emailvalidation.io/v1/info?email={email}"
headers = CaseInsensitiveDict()
headers["apikey"] = "your-api-key-here"
response = requests.get(url, headers=headers)
return response.json()
print(is_valid("support@emailvalidation.io"))
print(is_valid("venip42579@jdsdhak.com"))
is_valid 함수는 이메일 주소를 인자로 받아 해당 이메일 주소가 포함된 URL을 구성한 뒤 emailvalidation.io API를 호출합니다. requests.structures 모듈의 CaseInsensitiveDict 클래스는 대소문자를 구분하지 않는 키를 가진 딕셔너리를 생성하는 데 사용되며, 이를 통해 요청 헤더에 API 키를 설정합니다. 마지막으로 함수는 JSON 응답을 반환합니다.
마지막으로 서로 다른 두 이메일 주소로 is_valid 함수를 두 번 호출하여, 유효한 이메일 주소(support@emailvalidation.io)와 유효하지 않은 이메일 주소(venip42579@jdsdhak.com)를 모두 검증할 수 있는지 확인합니다.
실행 결과:
{
"email": "support@emailvalidation.io",
"user": "support",
"tag": "",
"domain": "emailvalidation.io",
"smtp_check": true,
"mx_found": true,
"did_you_mean": "",
"role": true,
"disposable": false,
"score": 0.64,
"state": "deliverable",
"reason": "valid_mailbox",
"free": false,
"format_valid": true,
"catch_all": "None"
}
{
"email": "venip42579@jdsdhak.com",
"user": "venip42579",
"tag": "",
"domain": "jdsdhak.com",
"smtp_check": false,
"mx_found": false,
"did_you_mean": "",
"role": false,
"disposable": false,
"score": 0.64,
"state": "undeliverable",
"reason": "invalid_mx",
"free": false,
"format_valid": true,
"catch_all": "None"
}
응답에 포함된 각 키의 의미는 공식 문서에서 자세히 확인할 수 있습니다. emailvalidation.io가 반환한 JSON 응답을 기준으로 이메일 주소의 유효 여부를 판단하려면 다음 필드들을 확인해야 합니다.
format_valid:true이면 이메일 주소의 형식이 올바르다는 뜻이고,false이면 유효하지 않은 이메일 주소입니다.mx_found:true이면 해당 도메인에 MX 레코드가 하나 이상 존재한다는 뜻이고,false이면 도메인 자체가 유효하지 않습니다.smtp_check:true이면 해당 이메일 주소에 유효한 메일박스가 있다는 뜻이고,false이면 메일박스가 유효하지 않습니다.state: 이메일 주소의 현재 상태를 나타내며, 값은 "deliverable" 또는 "undeliverable"입니다.
따라서 is_valid 함수를 수정해 JSON 객체 대신 불리언(Boolean) 값을 반환하도록 만들 수 있습니다.
import requests
from requests.structures import CaseInsensitiveDict
def is_valid(email: str):
url = f"https://api.emailvalidation.io/v1/info?email={email}"
headers = CaseInsensitiveDict()
headers["apikey"] = "your-api-key-here"
response = requests.get(url, headers=headers)
if response.status_code == 200:
json_resp = response.json()
format_valid = json_resp["format_valid"]
mx_found = json_resp["mx_found"]
smtp_check = json_resp["smtp_check"]
state = json_resp["state"]
return format_valid and mx_found and smtp_check and state == "deliverable"
return False
print(is_valid("support@emailvalidation.io"))
print(is_valid("venip42579@jdsdhak.com"))
실행 결과:
True
False
다음 섹션에서는 이 함수를 활용해 회원가입 과정에서 이메일을 검증하는 방법을 살펴보겠습니다.
Flask에서 기본 사용자 인증 설정하기
이 섹션에서는 Flask에서 기본적인 사용자 인증을 설정하는 단계를 진행합니다. 여기서는 이전 글에서 기본 사용자 인증 구현 방법을 설명할 때 사용했던 코드를 활용하겠습니다.
먼저 GitHub 저장소에서 코드를 email-validation 폴더로 가져옵니다.
git init
git remote add origin https://github.com/ashutoshkrris/Flask-User-Authentication.git
git pull origin main
참고: git clone https://github.com/ashutoshkrris/Flask-User-Authentication.git . 명령어는 디렉터리가 비어 있지 않기 때문에 이 경우 실행되지 않습니다.
다음으로 애플리케이션 실행에 필요한 의존성이 담긴 requirements.txt 파일이 보일 것입니다. 아래 명령어로 의존성을 설치합니다.
pip install -r requirements.txt
모든 의존성 설치가 끝나면 프로젝트에 필요한 환경 변수를 추가해야 합니다. 프로젝트에는 모든 환경 변수가 담긴 .env 파일이 포함되어 있습니다. 다음 명령어를 실행해 .env 파일의 환경 변수를 모두 내보냅니다.
source .env
그다음 데이터베이스를 생성해야 합니다. 이 프로젝트는 Flask-Migrate를 사용하므로 다음 명령어만으로 간단히 데이터베이스를 생성할 수 있습니다.
python manage.py db init
python manage.py db migrate
python manage.py db upgrade
이제 아래 명령어로 애플리케이션을 실행할 수 있습니다.
python manage.py run
애플리케이션이 실행되면 웹 브라우저에서 https://localhost:5000/login에 접속해 애플리케이션을 확인할 수 있습니다.
프로젝트 구조를 살펴보면, 소스 코드가 담긴 src 폴더와 단위 테스트가 담긴 tests 폴더가 있습니다.
이 외에도 애플리케이션의 설정값이 담긴 config.py 파일과, Flask-CLI를 사용해 애플리케이션 실행 및 테스트 관련 다양한 명령어를 추가하는 manage.py 파일이 있습니다. 이미 익숙한 .env, requirements.txt 같은 파일들도 함께 들어 있습니다.
src 폴더에는 네 개의 하위 폴더(accounts, core, templates, static)가 있습니다. templates와 static 폴더에는 각각 HTML 파일과 CSS, 이미지, JavaScript 같은 정적 파일이 들어 있습니다. 나머지 두 폴더인 accounts와 core는 Flask Blueprint 개념을 활용해 애플리케이션의 각 부분에 해당하는 코드를 담고 있습니다.
Flask 애플리케이션 구현에 대해 더 자세히 알고 싶다면 관련 튜토리얼을 참고하세요.
Flask 앱에 이메일 검증 서비스 통합하기
지금까지의 상태에서는 이메일 주소의 유효 여부와 상관없이 어떤 이메일 주소로든 회원가입이 가능했습니다.
하지만 무작위이거나 잘못된 이메일 주소가 데이터베이스를 어지럽히는 것은 바람직하지 않습니다. 따라서 사용자를 등록하기 전에 이메일 주소를 검증하는 것이 좋습니다. 이메일 주소가 유효하다면 회원가입을 정상적으로 진행하면 됩니다.
공개 노출 없이 값을 읽어올 수 있도록 이메일 검증 API 키를 .env 파일에 추가합니다.
export SECRET_KEY=fdkjshfhjsdfdskfdsfdcbsjdkfdsdf
export DEBUG=True
export APP_SETTINGS=config.DevelopmentConfig
export DATABASE_URL=sqlite:///db.sqlite
export FLASK_APP=src
export FLASK_DEBUG=1
export API_KEY=your-api-key-here
your-api-key-here 부분을 실제 API 키로 교체하세요. 그다음 환경 변수를 내보내기 위해 다음 명령어를 다시 실행합니다.
source .env
이제 src 폴더 안의 accounts 하위 폴더에 utils.py 파일을 생성합니다. 이 파일에는 이메일을 검증하는 유틸리티 함수가 들어갑니다. 파일에 아래 코드를 추가합니다.
import requests
from requests.structures import CaseInsensitiveDict
from decouple import config
def is_valid(email: str):
url = f"https://api.emailvalidation.io/v1/info?email={email}"
headers = CaseInsensitiveDict()
headers["apikey"] = config("API_KEY")
response = requests.get(url, headers=headers)
if response.status_code == 200:
json_resp = response.json()
format_valid = json_resp["format_valid"]
mx_found = json_resp["mx_found"]
smtp_check = json_resp["smtp_check"]
state = json_resp["state"]
return format_valid and mx_found and smtp_check and state == "deliverable"
return False
앞서 언급했듯이 is_valid() 함수는 이메일 주소의 유효 여부를 나타내는 불리언 값을 반환합니다. 주목할 점은 이 함수가 API 키 값을 코드에 직접 하드코딩하지 않고 환경 변수에서 가져온다는 것입니다.
다음으로 forms.py 파일의 RegisterForm 클래스에는 validate 메서드가 있습니다. 이 메서드는 회원가입 과정에서 사용자가 제출한 입력 데이터의 유효성을 검사하는 역할을 담당합니다.
기존에는 이 메서드가 이메일이 이미 등록되어 있는지와 비밀번호가 일치하는지만 확인했습니다. 이제 여기에 이메일이 유효한지 확인하는 추가 검증 로직을 넣을 수 있습니다. 수정된 validate 메서드는 다음과 같습니다.
...
from src.accounts.utils import is_valid
...
class RegisterForm(FlaskForm):
...
def validate(self):
initial_validation = super(RegisterForm, self).validate()
if not initial_validation:
return False
if not is_valid(self.email.data):
self.email.errors.append("Email is invalid")
return False
user = User.query.filter_by(email=self.email.data).first()
if user:
self.email.errors.append("Email already registered")
return False
if self.password.data != self.confirm.data:
self.password.errors.append("Passwords must match")
return False
return True
validate 메서드에서 self.email.data(즉, 사용자가 입력한 이메일 주소)가 유효하지 않으면 self.email.errors 리스트에 오류 메시지를 추가하고 False를 반환합니다. 이는 사용자 데이터가 유효하지 않다는 의미입니다.
이제 애플리케이션을 실행하고 회원가입을 시도하면 결과를 직접 확인할 수 있습니다. 유효한 경우와 유효하지 않은 경우를 모두 보여주는 데모입니다.
이메일 검증 서비스의 다른 활용 사례
회원가입 시 사용자 이메일을 검증하는 것 외에도 이메일 검증 서비스는 다양한 용도로 활용될 수 있습니다. 대표적인 예는 다음과 같습니다.
- 이메일 리스트 정리: 이메일 검증 서비스를 사용하면 유효하지 않거나 존재하지 않거나 위험한 이메일 주소를 제거해 이메일 리스트를 정리할 수 있습니다. 이를 통해 이메일 도달률을 높이고 메일이 실제 수신자에게 전달되도록 보장할 수 있습니다.
- 사기 행위 방지: 가짜 계정 생성이나 허위 주문 같은 사기 행위를 탐지하는 데에도 활용할 수 있습니다. 이러한 활동과 연관된 이메일 주소를 검증하면 사기 행위를 사전에 차단할 수 있습니다.
- 마케팅 캠페인 강화: 이메일 마케팅 캠페인의 정확성과 효과를 높이는 데도 도움이 됩니다. 이메일 주소가 유효하고 활성 상태임을 보장하면 기업은 이메일 도달률을 높이고 캠페인 성과를 전반적으로 개선할 수 있습니다.
전반적으로 이메일 검증 서비스는 사용자 데이터의 정확성과 유효성을 확보하고, 사기를 방지하며, 사용자 경험을 개선하는 강력한 도구입니다.
결론
이메일 검증 서비스는 사용자 이메일 주소 확인이 필요한 모든 애플리케이션에 매우 유용한 도구입니다. 오류를 방지하고 사용자 입력 데이터가 올바른지 보장하려면 이메일 주소의 유효성을 반드시 확인해야 합니다.
이 글에서는 emailvalidation.io API를 사용해 Python에서 이메일 주소를 검증하는 방법을 살펴봤습니다. 또한 사기 탐지, 이메일 마케팅 등 이메일 검증 서비스의 다른 잠재적 활용 사례도 함께 알아봤습니다.
애플리케이션에 이메일 검증 서비스를 도입하면 사용자 경험을 개선하고 데이터의 정확성과 최신성을 보장할 수 있습니다.
추가 학습 자료
- Blueprint로 Flask 앱 체계적으로 구성하는 방법
- Flask 앱에서 이메일 인증(Verification) 설정하는 방법
- emailvalidation.io 공식 문서