본문 바로가기
파이썬/파이썬 플라스크

ep 4. 로그인 처리

by L_SU 2022. 7. 26.
{% extends 'base.html' %} {% block title %}Login{% endblock %} {% block header
%}
<header
  class="masthead"
  style="background-image: url('{{ url_for('static', filename='assets/img/login-bg.jpg') }}')"
>
  <div class="container position-relative px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 justify-content-center">
      <div class="col-md-10 col-lg-8 col-xl-7">
        <div class="site-heading">
          <h1>Login</h1>
        </div>
      </div>
    </div>
  </div>
</header>
{% endblock %} {% block content %}

<!-- Main Content-->
<main class="mb-4">
  <div class="container px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 justify-content-center">
      <div class="col-md-10 col-lg-8 col-xl-7">
        <div class="my-5">
          <form id="contactForm" method="POST">
            <div class="form-floating">
              <input
                class="form-control"
                id="email"
                type="text"
                placeholder="Enter your email..."
                name="email"
                data-sb-validations="required"
              />
              <label for="email">Email</label>
              <div class="invalid-feedback" data-sb-feedback="email:required">
                A Email is required.
              </div>
            </div>
            <div class="form-floating">
              <input
                class="form-control"
                id="password"
                type="password"
                placeholder="Enter your password..."
                name="password"
                data-sb-validations="required,email"
              />
              <label for="password">Password</label>
              <div
                class="invalid-feedback"
                data-sb-feedback="password:required"
              >
                An password is required.
              </div>
              <div
                class="invalid-feedback"
                data-sb-feedback="password:required"
              >
                Email is not valid.
              </div>
            </div>
            <br />
            <!-- Submit success message-->
            <!---->
            <!-- This is what your users will see when the form-->
            <!-- has successfully submitted-->
            {#
            <div class="d-none" id="submitSuccessMessage">
              #} {#
              <div class="text-center mb-3">
                #} {#
                <div class="fw-bolder">Form submission successful!</div>
                #} {# To activate this form, sign up at#} {# <br />#} {#
                <a href="https://startbootstrap.com/solution/contact-forms"
                  >https://startbootstrap.com/solution/contact-forms</a
                >#} {#
              </div>
              #} {#
            </div>
            #}
            <!-- Submit error message-->
            <!---->
            <!-- This is what your users will see when there is-->
            <!-- an error submitting the form-->
            {#
            <div class="d-none" id="submitErrorMessage">
              #} {#
              <div class="text-center text-danger mb-3">
                Error sending message!
              </div>
              #} {#
            </div>
            #}
            <!-- Submit Button-->
            <button
              class="btn btn-primary text-uppercase"
              id="submitButton"
              type="submit"
            >
              LOGIN
            </button>
          </form>
        </div>
      </div>
    </div>
  </div>
</main>

{% endblock %}

위는 먼저 로그인 기능 구현을 위한 html 코드이다. login.html이라는 파일을 만들어 위와 같이 코드를 입력해주면 되겠다.

{% extends 'base.html' %}

{% block title %}Signup{% endblock %}

{% block header %}
    <header class="masthead" style="background-image: url('{{ url_for('static', filename='assets/img/signup-bg.jpg') }}')">
        <div class="container position-relative px-4 px-lg-5">
            <div class="row gx-4 gx-lg-5 justify-content-center">
                <div class="col-md-10 col-lg-8 col-xl-7">
                    <div class="site-heading">
                        <h1>Sign Up</h1>
                    </div>
                </div>
            </div>
        </div>
    </header>
{% endblock %}

{% block content %}

    <!-- Main Content-->
    <main class="mb-4">
        <div class="container px-4 px-lg-5">
            <div class="row gx-4 gx-lg-5 justify-content-center">
                <div class="col-md-10 col-lg-8 col-xl-7">
                    <div class="my-5">
                        <form id="contactForm" method="POST">
                            <div class="form-floating">
                                <input class="form-control" id="email" type="text" placeholder="Enter your email..." name="email"
                                       data-sb-validations="required"/>
                                <label for="email">Email</label>
                                <div class="invalid-feedback" data-sb-feedback="email:required">A Email is required.
                                </div>
                            </div>
                            <div class="form-floating">
                                <input class="form-control" id="username" type="text" placeholder="Enter username..." name="username"
                                       data-sb-validations="required"/>
                                <label for="email">Username</label>
                                <div class="invalid-feedback" data-sb-feedback="username:required">A Username is
                                    required.
                                </div>
                            </div>
                            <div class="form-floating">
                                <input class="form-control" id="password1" type="password" name="password1" name="password1"
                                       placeholder="Enter your password..."
                                       data-sb-validations="required,email"/>
                                <label for="password1">Password</label>
                                <div class="invalid-feedback" data-sb-feedback="password:required">An password is
                                    required.
                                </div>
                                <div class="invalid-feedback" data-sb-feedback="password:required">Password is not valid.
                                </div>
                            </div>
                            <div class="form-floating">
                                <input class="form-control" id="password2" type="password" name="password2" name="password2"
                                       placeholder="Enter your password..."
                                       data-sb-validations="required,email"/>
                                <label for="password2">Password Again</label>
                                <div class="invalid-feedback" data-sb-feedback="password:required">Enter your password again!
                                </div>
                                <div class="invalid-feedback" data-sb-feedback="password:required">Password again is not valid.
                                </div>
                            </div>
                            <br/>
                            <!-- Submit success message-->
                            <!---->
                            <!-- This is what your users will see when the form-->
                            <!-- has successfully submitted-->
                            {#                            <div class="d-none" id="submitSuccessMessage">#}
                            {#                                <div class="text-center mb-3">#}
                            {#                                    <div class="fw-bolder">Form submission successful!</div>#}
                            {#                                    To activate this form, sign up at#}
                            {#                                    <br/>#}
                            {#                                    <a href="https://startbootstrap.com/solution/contact-forms">https://startbootstrap.com/solution/contact-forms</a>#}
                            {#                                </div>#}
                            {#                            </div>#}
                            <!-- Submit error message-->
                            <!---->
                            <!-- This is what your users will see when there is-->
                            <!-- an error submitting the form-->
                            {#                            <div class="d-none" id="submitErrorMessage">#}
                            {#                                <div class="text-center text-danger mb-3">Error sending message!</div>#}
                            {#                            </div>#}
                            <!-- Submit Button-->
                            <button class="btn btn-primary text-uppercase" id="submitButton" type="submit">
                                Sign Up
                            </button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </main>



{% endblock %}

로그인 기능이 있으려면 회원가입도 있어야 되지 않겠는가? 위의 코드가 회원가입 코드이다. 이는 signup.html 파일을 생성해 작성해주면 되겠다.

 

 

로그인 기능을 구현하려면 어떻게 해야할까?

방식을 생각하는데엔 큰 어려움이 있지 않을 것이다. 단순히 내가 입력하고, 그 입력한 값을 받아서 처리를 한다. 고 생각할 수 있으니까

 

우리는 이를 POST 형식으로 데이트를 보내줄 것이고, GET으로 받아 처리해줄 것이다.

로그인 처리

위의 사진과 같이 auth.py에 주소 , methods=['GET', 'POST'] 를 써주면 이를 post와 get으로 처리할 수 있게 된다.

 

로그인 코드

로그인 코드 파일에 가서 보면, 아래서 두번째 줄에서 POST형식으로 처리를 하는 것을 확인할 수 있다.

 

 

로그인 코드 2

좀 더 내려가보면 input태크에서 name 옵션을 사용한 것을 볼 수 있는데 이는 데이터가 전송이 되고난 뒤 이 데이터가 어떤 데이터인지 확인을 하기위해 사용된다. 이제 우리는 보냈으니 이 데이터를 받아야 된다

회원가입 이해를 돕기위한 예제

받기 위해서 우리는 request라는 옵션을 사용한다. 이를 위해 맨 위 import 문장에 request를 추가해주고 다음과 같이 코드를 짜여주고 어떤 기능을 하는지 살펴보자

이해를 돕기 위한 예제

위처럼 sign-up 주소로 접속해 정보를 입력해주고 버튼을 눌러주게 되면

터미널에 나타난 예제

터미널에 다음과 같이 정보를 받아 출력을 해주는 것을 볼 수 있다.

 

이제 본격적으로 로그인 기능 작업에 앞서 데이터베이스가 존재해야 하기때문에 다음과 같이 데이터베이스가 존재하지 않는다면 생성해주는 코드를 작성하자

__init__.py 코드

새로 추가된 문장부터 설명하자면,

db = SQLAlchemy() 문장은 db라는 이름으로 SQLAlchemy()을 받겠다는 건데 SQLAlchemy()는 파이썬의 ORM으로 파이썬의 클래스와 데이터베이스를 매핑해주는 역할이라고 생각해주면 된다.

 

DB_NAME = "blog_db" 이는 데이터베이스 파일의 이름을 blog_db로 설정해준다는 의미이다.

 

    app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlit:///{DB_NAME}'
    db.init_app(app)
 
윗 줄부터 살펴보면 SQLAlchemy()에서 사용할 데이터베이스의 위치를 지정해준 것이다.
두번째 줄은 필자가 이해한 바로는 데이터베이스 설정에 사용할 응용 프로그램을 초기화 해주는 것이다.

 

데이터 베이스생성

위의 코드는 데이터베이스가 존재하지 않으면 데이터베이스를 생성해주기 위한 코드이다.

코드가 직관적이라 설명이 따로 필요 없을 거 같아 설명은 스킵하겠다.

 

models.py

그리곤 models.py 파일을 하나를 만들어 다음과 같은 코드를 짜줌으로 서 모델을 하나 생성했다. User 모델 안의 코드를 살펴보면 id를 설정해 이를 고유키로 설정해줬고, email과 username은 str형으로 글자수 150제한, 유니크 옵션으로 중복되지 않게 했다. 비밀번호엔 별다른 옵션 없이 글자수 제한만 주었으며, 마지막 creat_at으로 현재 시간으로 작성시간이 저장되게 했다.

__init__.py import 추가

첫번째 줄부터 살펴보자면 로그인 매니저를 import 해줬는데 이는 로그인 기능을 구현함에 있어 플라스크에서 쉽게 구현할 수 있도록 제공해주는 라이브러리이다.

그리고 다음과 같이 models 파일의 user을 import 해줬는데 원래 플라스크 실습을 다른 방법을 통해 접해본 사람이라면, 지금의 방법이 부자연스럽다고 생각할 수도 있다. db와 DB_NAME은 __init__ 에 있고, get_user_model 이라는 함수를 써서 받는게 아닌 import user로 바로 받으니까 다만, 필자는 알 수 없는 문제로 db를 models에서 import 해줄 때 문제가 발생해 다음과 같이 조금 꼬아서 코드를 짜는 것으로 해결할 수 있었고, 그래서 이런 방법을 고수하게 되었다.

 

__init__.py 코드 추가

로그인 매니저는 다음과 같이 활용되었는데, 위의 두줄은 로그인 매니저를 객체로 받아, 로그인이 필요한 곳에서 로그인이 되지 않은 유저가 접근하게 될 경우 로그인 화면으로 이동시켜주는 코드이다. 그 아래의 코드들은받은 id로 부터 데이터베이스에 있는 테이블 정보에 접근을 하게 해줘 로그인이 되어있다면, 로그인 페이지로 안 가게끔 해주는 코드들이다.

 

로그인 준비는 끝냈으니 회원가입을 구현해보겠다. 먼저 회원가입에 필요한 라이브러리를 설치해야 한다. pip install flask-wtf, pip install email-validator 명령을 터미널에 입력해 라이브러리를 설치한다.

 

forms.py

그리고 forms.py에 다음과 같이 코드를 짜주는데, 내용이 이 역시 직관적이라 알아보기 쉬워 따로 설명을 하지는 않겠다.

 

auth.py 소스 추가

그리곤 auth.py에 다음과 같이 코드를 추가해주면 되는데 위에서부터 폼에서부터 데이터를 받아와 데이버테이스에 이미 있는 데이터인지 확인을 해주고, 그 아래 띄어쓰기 된 코드에서 중복검사를 해준다. 중복한다면 if문과 elif문에 각각 뭐가 중복하는 지에 따라 걸리게 되는 문이 다를 거고, 만약 위의 과정에서 중복되지 않는다면 폼에서 받아온 데이터를 새로운 유저로 저장한다. 저장이 완료 되었으면 home으로 리다이렉트 된다. 이 페이지에 POST방식이 아닌 GET요청을 보낸다면 회원가입 템플릿을 보여주게 된다.

signup.html 코드 추가

signup.html 의 폼코드 아래에 다ㅏ음과 같이 입력해줘야 폼이 정상작동한다.

 

폼에서 가져온 데이터들을 데이터베이스에 저장하는 거까지 모두 구현을 끝냈지만 이대로라면 데이터베이스에 접근할 수 있는 누구든지 유저들의 정보를 알 수 있을 것이다. 이를 막기위해 암호화시키는 작업이 필요한데 이를 우리는 해싱이라고 한다. 비밀번호를 해싱하기 위해서

from werkzeug.security import generate_password_hash, check_password_hash 
 
를 import 해주고, 비밀번호 받는 부분을 변경해주면 되는데 필자는 이미 아까 작성을 했다 데이터가 날라가 다시 작성을 하고 있는 거라 비밀번호 받는 부분이 변경되어 있는 것을 확인할 수 있다. 이렇게 코드를 완성한 후 사이트에서 회원가입을 진행해보면

데이터베이스

다음과 같이 DB 브라우저로 비밀번호 해싱이 완료된 것을 확인할 수 있다. 이제 회원가입에 성공했으니 로그인을 해보겠다.

forms.py

우선 로그인에 필요한 다음과 같은 폼 코드를 작성해주면 된다.

auth.py

그리고 다음과 같이 로그인 사이트에 대한 부분의 코드를 짜주면 되는데, 회원가입에서 했던 것과 유사하여 빠르게 집고 넘어가자면 똑같이 폼에서 데이터를 받아와 이메일로 유저를 찾고, 이메일이 유효하다면 비밀번호가 해싱되어 있음으로 이를 체크해서 확인하고 맞다면 로그인을 성공시킨다. 틀렸다면 비밀번호가 틀렸다는 문구를 나타내고, 만약 이메일이 존재하지않다면 존재하지 않다고 화면에 뜨게 한다. 그리고 마찬가지로 GET요청이 들어온다면 로그인 화면을 나타나게 한다.

 

이후 아까와 같이 login.html로 들어가 폼 사이에

{{form.csrf_token}}
 
를 넣어주면 된다.
 
이제 진짜 거의 다 왔다.. 로그아웃 기능만 넣어주면 된다.
auth.py

다음과 같이 코드를 짜주면 되는데 @login_required를 넣어준 이유는 로그아웃은 로그인 되어있는 상태에서만 할 수 있기 떄문이다.

 

base.html

그리고 다음과 같은 코드를 base.html의 nav 태그 바로 아래에 입력해주면 에러 메시지를 볼 수 있다.

 

페이지

그럼 로그인 같은 동작들을 할 때 우리가 아까 넣어준 문장들이 출력되는 것을 확인할 수 있다.

 

base.html

위의코드를 이렇게 변경해보자

 

그럼 다음과 같이 에러 메시지가 좀 더 보기 좋게 나타나는 것을 알 수 있다. 카테고리를 사용해 에러인지 아닌지 확인해 구별해준 것이다.

 

base.html

그리곤 다음과 같이 로그인과 회원가입등의 기능을 네비게이션 바에 나타나게 해주기 위해서 다음과 같은 코드를 추가해주고,

auth.py

auth.py에 다음과 같은 코드를 추가해준다. 로그인 외에 회원가입 주소를 처리하는 부분에도 같은 식으로 user_current_user을 추가해주고, views코드에도 처리해준다.

 

네비게이션 바

그럼 다음과 같이 네비게이션에 추가된 것을 확인할 수 있다.

 

base.html

마지막으로 base에서 아까 로그인과 회원가입을 추가했던 부분을 이렇게 변경해주면

다음과 같은 이쁜 동적인 네비게이션을 만들 수 있다!