웹 프로그래밍/Django

[Django] 회원가입 구현과 패스워드 암호화(bcrypt)

by seokii 2022. 12. 13.
728x90
반응형

1. models.py 작성

장고에서 데이터베이스에 데이터를 저장할 구조인 모델 부분의 코드를 작성합니다.

해당 글에서는 장고의 기본 DB인 sqlite3가 아닌 PostgreSQL을 연동해 사용했습니다.

PostgreSQL의 연동법은 이전 포스팅을 참고하시기 바랍니다.

https://seokii.tistory.com/199

 

[Django] PostgreSQL 설치와 장고에 연동하기

1. PostgreSQL 설치 PostgreSQL 설치는 Windows 10 기준으로 작성하도록 하겠습니다. 장고와 연동하는 부분은 상관없이 진행하시면 됩니다. 다운로드 주소 : https://www.postgresql.org/download/ PostgreSQL: Downloads www

seokii.tistory.com

 

- models.py 코드

from django.db import models

class User(models.Model):
    user_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32, verbose_name='사용자 이름')
    email = models.EmailField(max_length=128, verbose_name='사용자 이메일')
    password = models.CharField(max_length=300, verbose_name='비밀번호')
    is_admin = models.BooleanField(default=False, verbose_name='관리자')
    create_dt = models.DateField(auto_now_add=True, verbose_name='가입 날짜')
    is_authenticated = models.BooleanField(default=False, verbose_name='이메일 인증 여부')

    class Meta:
        # 테이블 이름 설정
        db_table = 'User'

생성한 앱 내의 models.py 코드 내용입니다.

기본적인 user_id 값과 회원 가입시 필요한 사용자의 정보를 받기 위해 필요한 정보들을 지정했습니다.

사용자의 이름, 이메일, 비밀번호, 관리자 여부, 회원가입 날짜, 이메일 인증 여부의 내용을

name, email, password, is_admin, create_dt, is_authenticated의 컬럼명으로 지정했습니다.

각 컬럼의 속성에 맞게 CharField, EmailField, BooleanField, DateField를 사용해 지정했습니다.

 

비밀번호의 길이가 300자로 길게 설정한 이유는 사용자로부터 받은 문자열을 암호화 시키면 길이가 매우 길어지기 때문에 여유있게 설정했습니다.

id값, 생성 날짜, 관리자 여부, 이메일 인증 여부의 내용은 지정한 기본 값으로 추가되도록 설정했습니다.

 

작성한 User클래스 안의 메타 클래스를 통해 구조화 시킨 DB에 대한 여러 부가 설정이 가능합니다.

db_table 정의하면 테이블의 이름을 지정할 수 있습니다.

(지정하지 않았다면, "앱 이름.User"의 형태로 테이블이 생성됨)

 

모델 파일의 코드 작성 후,

python manage.py makemigrations

python manage.py migrate

를 통해 연동된 DB에 테이블을 생성합니다.

 

2. html 작성

2.1. 네비게이션바 HTML 코드

페이지의 상단 네비게이션바에 대한 코드 내용입니다.

더보기
<!-- Header -->
<header>
    <div class="inner">

        <div class="sub-menu">
            <ul class="menu">
                <li><a href="/login">로그인</a></li>
                <li><a href="/signup">회원가입</a></li>
            </ul>
        </div>

        <ul class="main-menu">
            <li class="item">
                <div class="item__name">
                    <a href="/overview">Overview</a>
                </div>
                <div class="item__contents">
                    <div class="contents__menu">
                        <ul class="inner">
                            <li>
                                <a href="/overview"><h4>플랫폼 및 서비스 소개</h4></a>
                            </li>
                            <li>
                                <a href="/overview/platform_info"><h4>데이터 플랫폼 리스트 소개</h4></a>
                            </li>
                            <li>
                                <a href="/overview/eda"><h4>수집 데이터 EDA</h4></a>
                            </li>
                        </ul>
                    </div>
                </div>
            </li>
            <li class="item">
                <div class="item__name">
                    <a href="/search/category">Search</a>
                </div>
                <div class="item__contents">
                    <div class="contents__menu">
                        <ul class="inner">
                            <li>
                                <a href="/search/category"><h4>카테고리 별 검색</h4></a>
                            </li>
                            <li>
                                <a href="/search/detail"><h4>세부 데이터 검색</h4></a>
                            </li>
                        </ul>
                    </div>
                </div>
            </li>


            <li class="item">
                <div class="item__name">
                    <a href="/community">Community</a>
                </div>
                <div class="item__contents">
                    <div class="contents__menu">
                        <ul class="inner">
                            <li>
                                <a href="/community"><h4>커뮤니티 홈</h4></a>
                            </li>
                            <li>
                                <a href="/community/create"><h4>글 작성하기</h4></a>
                            </li>
                        </ul>
                    </div>
                </div>
            </li>

            <li class="item">
                <div class="item__name">
                    <a href="/support">고객지원</a>
                </div>
                <div class="item__contents">
                    <div class="contents__menu">
                        <ul class="inner">
                            <li>
                                <a href="/support"><h4>문의하기</h4></a>
                            </li>
                        </ul>
                    </div>
                </div>
            </li>
        </ul>
    </div>
</header>

 

2.2. 본문 내용 HTML

body 부분의 코드 내용입니다.

더보기
<body>
{% include 'apps/navbar.html' %}
<section class="signup">
    <div class="signup__card">
        <h1>
            <br/>
            <strong>Welcome!</strong> 데이터 플래닛에 오신걸 환영합니다.
            <br/><br/>
        </h1>

        <form method="POST">
            {% csrf_token %}
            <div class="signup__table">
                <!-- 이름 입력 -->
                <div class="joinRow pwDiv">
              <span>
                <label for="name"></label>
                <input type="text" id="userName" name="name" placeholder="이름을 입력하세요."
                       style="border: 1px solid #e5e5e5;">
              </span>
                </div>

                <!-- 이메일 입력 -->
                <div class="joinRow idDiv">
              <span style="height:36px">
                <label for="email" class="label_hidden">아이디(이메일)</label>
                <input type="text" id="userEmail" name="email" placeholder="이메일을 입력하세요.">

                  <!-- 인증번호 전송 -->
                <div id="a_sendcerti_div">
                  <a id="a_sendCerti" href="javascript:;" class="btn_gray floatR">인증번호 전송</a>
                </div>
              </span>
                </div>

                <!-- 추후 인증번호 인증 구현 -->
                <div id="div_checkCerti" class="joinRow authNoDiv" style="display: none;">
              <span style="height:36px">
              <label for="certiNum" class="label_hidden">인증번호 입력</label>
              <input type="text" id="certiNum" name="certiNum" placeholder="인증번호 입력" style="border: 1px solid #e5e5e5;">
              </span>
                    <a id="test1" href="javascript:fn_checkCertiNum();" class="btnWhite floatR">인증번호 확인</a>
                    <div id="div_userId" class="error ok" style="display:#none;  margin-top: 8px;">인증번호를 입력해주세요</div>
                </div>

                <!-- 비밀번호 입력 -->
                <div class="joinRow pwDiv">
              <span>
              <label for="password" class="label_hidden">비밀번호</label>
              <input type="password" id="userPassword" name="password" placeholder="비밀번호를 입력하세요."
                     style="border: 1px solid #e5e5e5;">
              </span>
                </div>

                <!-- 비밀번호 확인 -->
                <div class="joinRow pwDiv">
              <span>
              <label for="password_check" class="label_hidden">비밀번호 확인</label>
              <input type="password" id="userPassword2" name="password_check" title="password"
                     placeholder="비밀번호를 한번 더 입력하세요." style="border: 1px solid #e5e5e5;">
            </span>
                </div>

                <!-- 최종제출 -->
                <div class="submit_btn">
                    <input type="submit" placeholder="로그인">
                </div>
            </div>
        </form>
</section>
</div>
</body>

css 코드 내용은 올려놓지 않았습니다.

html 코드만으로는 아래와 같은 모습이 나오지 않습니다!

- html 코드에서 form태그와 form태그 안의 label과 input, submit 버튼의 구조만 참고하시면 됩니다.

- form태그에서 장고로 전달하는 인자 값은 name, email, password, password_check입니다.

 

3. bcrypt

장고에서는 bcrypt 라이브러리를 통해 입력한 값을 쉽게 암호화할 수 있습니다.

직접 암호화를 구현해도 되지만, 검증된 라이브러리를 통한 암호화를 추천드립니다.

pip install bcrypt

bcrypt는 암호화를 위한 .hashpw(), .encode(), .gensalt() 등의 기능을 제공합니다.

 

 

4. views.py 작성

장고에서는 앱 안에서 views.py를 작성해 url에 대한 요청 동작을 수행할 수 있습니다.

 

- views.py 코드

from django.shortcuts import render
import bcrypt, re
from django.http import JsonResponse, HttpResponse

from .models import User

def signup(request):
    if request.method == 'POST':
        name = request.POST['name']
        email = request.POST['email']
        password = request.POST['password']
        password_check = request.POST['password_check']

        if User.objects.filter(email=email).exists():
            # return JsonResponse({'message': 'ALREADY_EXISTS'}, status=400)
            return HttpResponse("<script>alert('이미 존재하는 이메일입니다.\\n회원 가입 페이지로 돌아갑니다.');"
                                "location.href='/signup';</script>")

        regex_email = '^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9-.]+$'
        if not re.match(regex_email, email):
            # return JsonResponse({'message': 'INVALID_EMAIL'}, status=400)
            return HttpResponse("<script>alert('이메일 형식에 일치하지 않습니다.\\n회원 가입 페이지로 돌아갑니다.');"
                                "location.href='/signup';</script>")

        if password != password_check:
            # return JsonResponse({'message': '비밀번호 불일치!'}, status=400)
            return HttpResponse("<script>alert('비밀번호 불일치!\\n회원 가입 페이지로 돌아갑니다.');"
                                "location.href='/signup';</script>")

        password_encode = password.encode('utf-8')
        password_crypt = bcrypt.hashpw(password_encode, bcrypt.gensalt()).decode('utf-8')

        User.objects.create(name=name, email=email, password=password_crypt)
        # return JsonResponse({'message': 'SUCCESS!'}, status=201)
        return HttpResponse("<script>alert('회원가입 완료.\\n메인 페이지로 돌아갑니다.');"
                            "location.href='/';</script>")

    elif request.method == 'GET':
        return render(request, 'apps/signup.html')

GET 방식일 때는 단순히 작성한 페이지를 렌더링하도록 구현했습니다.

페이지에서 사용자가 회원가입을 위해 정보를 입력하고 제출 버튼을 누르면, POST 요청을 진행합니다.

구현한 회원 가입 방식은 다음과 같습니다.

1. 사용자가 회원 정보를 입력 후 제출 버튼을 누른다. (POST 요청 발생)

2. 요청을 받아 name, email, password, password_check 내용을 받아온다.

3. 연동된 DB의 email필드와 사용자가 작성한 정보를 비교해 이미 존재하는 이메일이라면,

    스크립트를 통해 alert문을 띄워주고, 회원 가입 페이지로 다시 돌아오도록 함.

4. 정규표현식을 사용해 사용자가 이메일 작성 칸에 이메일 형식으로 작성하지 않았을 경우,

    3과 같은 예외 처리를 진행함.

5. 사용자가 작성한 비밀번호, 비밀번호 확인 부분의 내용이 일치하지 않을 경우 3과 같은 예외 처리 진행.

6. 모든 조건을 통과했다면, 암호를 인코딩하고 bcrypt를 통해 패스워드 암호화를 진행한다.

    (.encode를 사용하는 이유는 utf-8 유니코드로 받은 입력 값을 bcrypt에 맞는 형태로 변환하기 위해서)

7. 사용자가 입력한 정보(이름, 이메일)와 암호화된 패스워드를 연동된 DB의 User테이블에 저장하고,

    회원가입 완료라는 알림과 함께 메인 페이지로 이동한다.

 

4. 결과

구현한 기능에 대한 결과입니다.

형태에 맞는 값을 입력하고, 제출 버튼을 누르면

연동된 DB에 알맞은 값이 추가된 것을 확인할 수 있습니다.

암호화도 정상적으로 이루어졌습니다.

 

 

728x90
반응형

댓글