코딩캠프/AI 웹개발 취업캠프

[AI 웹개발 취업캠프] 70Day - 프로젝트 21일차

고랑E 2023. 10. 30. 23:34
728x90

인증을 어떻게 처리할 것인가에 대해서 의견을 정하기위해 미뤄놨던 이메일 인증을 구현해보려고 한다.

Gmail의 SMTP(Simple Mail Transfer Protocol)를 이용해 이메일을 발송할거다.

 

우선 Gmail SMTP 설정 단계

 

  1. IMAP 사용
  2. 2단계 인증
  3. 앱 비밀번호

 

Gmail SMTP 설정

IMAP 사용

메일로 들어간다

https://mail.google.com/mail/u/1

 

Gmail

이메일 또는 휴대전화

accounts.google.com

 

 

우측 상단  설정 > 모든 설정 보기 를 눌러준다.

 

 

 

전달 및 POP/IMAP로 들어가준다.

 

 

 

왼쪽에 있던 사진을 오른쪽 사진처럼 IMAP 사용을 한다.

 

IMAP 사용, 자동 삭제 사용, IMAP 메일 수 제한하지 않음 후 변경사항 저장

IMAP 사용 빼고는 원하는대로 해도 된다.

 

 

 

2단계 인증 사용

우측 상단에 프로필을 누르고 Google 계정 관리 > 좌측 탭 - 보안

 

 

 

아래 내려보면 Google에 로그인하는 방법 > 2단계 인증

 

 

 

시작하기 > 계속

 

 

 

원하는 방식으로 인증 진행

 

 

 

사용 설정 완료

 

 

 

완료 하면 다음과 같은 화면이 나옵니다.

 

 

 

앱 비밀번호 생성

 

 

 

앱 비밀번호 생성할 이름을 아무거나?! 정해주고 만들기를 누릅니다.

 

 

 

그럼 앱 비밀번호가  aaaa bbbb cccc dddd 형식으로 생성됩니다.

 

꼭 복사 해두시고 메모 해두세요 생성했던 앱 비밀번호는 다시 볼 수 없습니다.
(새로 생성 하시면 되긴 합니다.)

 

 

설정 완료


Python 코드

이제 코드를 작성해볼까!!

fastapi-mail 패지키를 사용 해보자!!

 

패키지 설치

pip install fastapi-mail

 

깃허브에 올라온 예제를 이용해 이메일 발송까지 되는지 봐보자

https://github.com/sabuhish/fastapi-mail

 

GitHub - sabuhish/fastapi-mail: Fastapi mail system sending mails(individual, bulk) attachments(individual, bulk)

Fastapi mail system sending mails(individual, bulk) attachments(individual, bulk) - GitHub - sabuhish/fastapi-mail: Fastapi mail system sending mails(individual, bulk) attachments(individual, bulk)

github.com

 

.env

예시 코드에서 conf 부분을 env에서 불러오자

 

GMAIL_MAIL_SERVER=
GMAIL_MAIL_PORT=
GMAIL_MAIL_USERNAME=
GMAIL_MAIL_PASSWORD=
GMAIL_MAIL_FROM=
GMAIL_MAIL_FROM_NAME=

 

를 각 부분에 맞춰 작성한다.

GMAIL_MAIL_USERNAME=(이름)
GMAIL_MAIL_PASSWORD=(앱 비밀번호)
GMAIL_MAIL_FROM=(이메일)
GMAIL_MAIL_FROM_NAME=(발송할 이름)

 

main.py

# 상단에 fastapi-mail 관련 임포트
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType

# EmailSend 추가
from BE.schemas import UserCreate, UserLogin, UserUpdate, ModelCreate, EmailSend

 

.env에서 값 가져오기

GMAIL_MAIL_USERNAME = os.environ["GMAIL_MAIL_USERNAME"]
GMAIL_MAIL_PASSWORD = os.environ["GMAIL_MAIL_PASSWORD"]
GMAIL_MAIL_FROM = os.environ["GMAIL_MAIL_FROM"]
GMAIL_MAIL_SERVER = os.environ["GMAIL_MAIL_SERVER"]
GMAIL_MAIL_FROM_NAME = os.environ["GMAIL_MAIL_FROM_NAME"]

 

연결설정 변수에 저장

conf = ConnectionConfig(
    MAIL_USERNAME = GMAIL_MAIL_USERNAME,
    MAIL_PASSWORD = GMAIL_MAIL_PASSWORD,
    MAIL_FROM = GMAIL_MAIL_FROM,
    MAIL_PORT = 587,
    MAIL_SERVER = GMAIL_MAIL_SERVER,
    MAIL_FROM_NAME = GMAIL_MAIL_FROM_NAME,
    MAIL_STARTTLS = True,
    MAIL_SSL_TLS = False,
    USE_CREDENTIALS = True,
    VALIDATE_CERTS = True
)

 

api

@app.post("/send-mail")
async def simple_send(email: EmailSend):
    html = """<p>Hi this test mail, thanks for using Fastapi-mail</p> """

    message = MessageSchema(
        subject = "Fastapi-Mail module",
        recipients = [email.email],
        body = html,
        subtype = MessageType.html
    )
    
    fm = FastMail(conf)
    await fm.send_message(message)
    
    return JSONResponse(status_code=200, content={"message": "email has been sent"})

 

 

shemas.py

# 상단에 EmailStr 추가

from pydantic import BaseModel, EmailStr
class EmailSend(BaseModel):
    email: EmailStr

 

이러고 테스트를 해보면

발송이 된다.

 

 

발송시간이 거의 2.5~3.5초 사이 걸리는거같다.. 흠.. 줄이는방법이 있을까.. 고민 해봐야할듯..

 

 

이메일 제목, 내용 부분을 수정하자

@app.post("/send-mail")
async def simple_send(email: EmailSend):
    html = """
    <div>
    <b>※ 인증번호 요청에 따른 발신 전용 메일입니다.</b>
    <br>
    <br>
    인증코드는 <b>CODE</b> 입니다.
    </div>
    """

    message = MessageSchema(
        subject = "인증코드 발송",
        recipients = [email.email],
        body = html,
        subtype = MessageType.html
    )
    
    fm = FastMail(conf)
    await fm.send_message(message)
    
    return JSONResponse(status_code=200, content={"message": "인증코드가 이메일로 발송되었습니다."})

 

의 결과가 

 

이제 랜덤코드를 만들고 그걸 메일이랑 같이 보내보자!!

 

랜덤을 생성하기위한 여러가지가있지만 secrets 모듈에 있는 token_hex 또는 token_urlsafe 함수를 사용할 거다.

아래 코드로 수정하고 발송 테스트를 해보자

 

    code = secrets.token_hex(3)
    html = f"""
    <div>
    <b>※ 인증번호 요청에 따른 발신 전용 메일입니다.</b>
    <br>
    <br>
    인증코드는 <b>{code}</b> 입니다.
    </div>
    """


    code = secrets.token_urlsafe(3)
    html = f"""
    <div>
    <b>※ 인증번호 요청에 따른 발신 전용 메일입니다.</b>
    <br>
    <br>
    인증코드는 <b>{code}</b> 입니다.
    </div>
    """

 

의 차이가 난다 근데 뭔가 인증코드로 보기엔 token_urlsafe 함수가 더 좋은거같다

 


이메일 인증 모델

 

alembic 으로 디비에 마이그레이션 하는 코드를 작성해 보자

일단 email_auths 테이블을 만들어보자

 

alembic revision -m "create email_auths table"

 

다음과 같은 명령어로 마이그레이션 파일을 만들고 기본으로 작성된 코드에서 upgrade, downgrade 함수부분만 수정해주면된다.

 

email, code 컬럼을 사용 할거니까

 

def upgrade():
    # email_auths 테이블
    op.create_table(
        'email_auths',
        sa.Column('id', sa.BigInteger, primary_key=True),
        sa.Column('email', sa.String(255), nullable=False),
        sa.Column('code', sa.String(8), nullable=False),
    )


def downgrade():
    op.drop_table('email_auths')

 

코드를 수정해주고

 

터미널에 db 업그레이드 명령어를 입력해준다.

 

alembic upgrade head

 

db 확인해보면 잘 만들어졌다.

 

 

models.py

 

모델을 정의해주고

 

class EmailAuth(Base):
    __tablename__ = "email_auths"

    id = Column(BigInteger, primary_key=True, index=True)
    email = Column(String(255), unique=True, index=True)
    code = Column(String(8))

 

shemas.py

 

code 부분을 추가해준다.

 

class EmailSend(BaseModel):
    email: EmailStr
    code: str = None

 

 main.py

 

# 상단에 email_code 추가
from BE.crud import create_user, get_user, verify_password, get_user_info, update_user_info, get_models, get_my_models, create_model, get_model, email_code

# 상단에 EmailSend 추가
from BE.schemas import UserCreate, UserLogin, UserUpdate, ModelCreate, EmailSend

 

api 쪽에서 db로 넘겨준다.

    email.code = code
    email_code(db, email)

 

crud.py

 

# 상단에 EmailAuth 추가
from BE.models import User, Model, EmailAuth

# 상단에 EmailSend 추가
from BE.schemas import UserCreate, UserUpdate, ModelCreate, EmailSend

 

이제 db에 넣어주는 코드를 작성해 보자 예전에 작성했던 회원가입, 모델 생성 코드랑 비슷하다

 

def email_code(db: Session, email: EmailSend):
    email_auth_data = email.model_dump()
    db_email_auth = EmailAuth(**email_auth_data)
    db.add(db_email_auth)
    db.commit()
    db.refresh(db_email_auth)
    return db_email_auth

 

로 코드를 작성하고 테스트를 해보자

 

 

기능 구현 끝~

 

 

 

본 후기는 정보통신산업진흥원(NIPA)에서 주관하는 <AI 서비스완성! AI+웹개발 취업캠프 - 프론트엔드&백엔드> 과정 학습/프로젝트/과제 기록으로 작성되었습니다.