[Flask] Flask-Mail로 SMTP - 인증 메일 발송

하복치·2023년 5월 19일

PYTHON

목록 보기
4/5

👉 구글 앱 비밀번호 발급받기

  • 인증메일 발송에 사용할 구글 계정의 보안 탭으로 들어간다.
  • 2단계 인증을 '사용'으로 설정하고 전화번호 인증까지 완료한다.
  • 앱 비밀번호를 생성하고 생성된 비밀번호를 복사하여 smtp 서버 접속 시 해당 비밀번호를 사용한다.

Flask-Mail 기본 설정

MAIL_USERNAME, MAIL_PASSWORD 등 보안과 관련된 정보는 config.py에 넣어 사용한다.

# config.py
MAIL_SERVER='smtp.gmail.com'
MAIL_PORT = 465
MAIL_USERNAME = '본인 이메일계정@gmail.com'
MAIL_PASSWORD = '발급받은 앱 비밀번호'
MAIL_USE_TLS = False  
MAIL_USE_SSL = True  

Flask-Mail 설치

pip install Flask-Mail

Signature 생성

def	make_signature(timestamp):
	secret_key = bytes(SMS_API_SECRET, 'UTF-8')
	method = "POST"
	uri = f'/sms/v2/services/{SERVICE_ID}/messages'
	message = method + " " + uri + "\n" + timestamp + "\n" + SMS_API_KEY
	message = bytes(message, 'UTF-8')
	return base64.b64encode(hmac.new(secret_key, message, digestmod=hashlib.sha256).digest()) 

SMTP 함수

def send_mail(cert_info, otp):
    import app
    mail = app.mail
    
    msg = Message('[골리단길] 이메일 인증번호', sender=MAIL_USERNAME, recipients=[cert_info])
    msg.body = '안녕하세요. 골리단길 입니다.\n인증번호를 입력하여 이메일 인증을 완료해 주세요.\n인증번호 : {}'.format(otp)
    mail.send(msg)
    response={'statusCode': '202', 'statusName': 'success'}
    return response

인증번호 생성

def generate_otp(email_address, otp_create_time):
  otp = str(randint(100000, 999999))
  session[f'otp_{email_address}'] = otp  # 세션에 인증번호 저장
  session[f'time_{email_address}'] = otp_create_time  # 인증번호 생성 시간 저장
  return otp

인증번호 메일 발송

@bp.route('/sendOtp', methods=['POST'])
def send_otp():
    email_address = request.get_json(force=True)['mail']
    otp_create_time = request.get_json(force=True)['createTime']
    
    # 이메일 주소 중복 체크
    user = User.query.filter_by(mail = email_address).first() 
    
    if user: 
        response={
        'resultCode': 409,
        'resultDesc': "Conflict",
        'resultMsg': "이미 가입된 계정입니다."
        }
        return Response(response=json.dumps(response), status=409, mimetype="application/json")
    else: 
        # 인증 코드 생성 함수 호출
        otp = generate_otp(email_address, otp_create_time)

        # SMTP 함수 호출
        result = send_mail(email_address, otp)  
        
        if result['statusCode'] == '202':
            response={
                'resultCode': 200,
                'resultDesc': "Success",
                'resultMsg': "메일이 발송되었습니다."
            }
            return Response(response=json.dumps(response), status=200, mimetype="application/json")
        else: 
            response={
                'resultCode': 401,
                'resultDesc': "Unauthorized",
                'resultMsg': "메일 전송에 실패했습니다."
            }
            return Response(response=json.dumps(response), status=401, mimetype="application/json")

인증번호 검증

@bp.route('/verifyOtp', methods=['POST'])
def verify_otp():
    otp = request.get_json(force=True)['otp'] 
    email_address = request.get_json(force=True)['mail']  

    session_otp = session.get(f'otp_{email_address}')  # 세션에 저장된 인증번호 가져오기
    session_time = session.get(f'time_{email_address}')  # 세션에 저장된 생성 시간 가져오기

    # 세션에 인증번호와 생성 시간이 저장되어 있지 않은 경우
    if not session_otp or not session_time:
        response={
            'resultCode': 404,
            'resultDesc': "Not Found",
            'resultMsg': "해당 데이터가 존재하지 않습니다."
            }
        return Response(response=json.dumps(response), status=404, mimetype="application/json")
    
    # 시간이 만료된 경우
    if time.time() - session_time > 180:
    	# 세션에서 인증번호와 생성 시간을 삭제
        session.pop(f'otp_{email_address}')  
        session.pop(f'time_{email_address}')
        
        response={
            'resultCode': 401,
            'resultDesc': "Unauthorized",
            'resultMsg': "인증번호 인증 시간이 만료되었습니다."
            }
        return Response(response=json.dumps(response), status=401, mimetype="application/json")
    
    # 검증에 성공한 경우
    if otp == session_otp:
    	# 세션에서 인증번호와 생성 시간을 삭제
        session.pop(f'otp_{email_address}')  
        session.pop(f'time_{email_address}')
        
        response={
                'resultCode': 200,
                'resultDesc': "Success",
                'resultMsg': "인증이 성공적으로 완료되었습니다."
                }
        return Response(response=json.dumps(response), status=200, mimetype="application/json")
    else:
        response={
            'resultCode': 401,
            'resultDesc': "Unauthorized",
            'resultMsg': "인증번호가 일치하지 않습니다."
            }
        return Response(response=json.dumps(response), status=401, mimetype="application/json")
  • 인증메일 전송 성공

1개의 댓글

comment-user-thumbnail
2023년 9월 30일

에러가 많네요... 깃허브 소스있나요?

답글 달기