본문 바로가기

회고록(TIL&WIL)

TIL 2022.05.30 Django공부 - 회원가입/로그인/로그아웃/로그인유지, csrf_token

django의 전체적인 프로그래밍 흐름(개인적인 생각)

1. APP 생성

2. Model 설계

3. APP 마이그레이션

4. 프로젝트와 user APP urls 연결

5. 생성한 APP에 urls.py 생성하여 views.py에서 호출될 함수들과 연결

6. views.py 에서 기능 구현

    - template과 연결하는 GET 요청

    - 기능들을 구현하는 POST 요청

7. template에서 ajax 요청으로 해당하는 url에 json데이터 형식으로 request

8. json데이터 받아와서 CRUD, auth 등 진행 

9. template에 출력할 데이터 response(render) or 페이지이동(redirect) or HttpResponse로 인자값 리턴

10. HttpResponse로 리턴할 경우 ajax에서 success 에서 해당 데이터 가공 후 처리 구현

 

1~4번까지는 기획단계에서 기능에 따라 분류하여 작업할 때 미리 사전작업으로 한번만 하면되고

5번부터는 기능을 하나씩 구현할 때마다 해줘야하는 부분들이다.

새로운 기능을 추가할때는 테이블 수정이 있을 수 있기 때문에 다시금 2번부터 진행하면서 수정해야한다.


회원가입

1. Model.py에서 DB데이터 생성

장고 내장 함수중 AbstractUser 클래스를 import해와서 UserModel클래스에 상속받아와서 추가로 필요한 부분만

오버라이딩해주는 식으로 작성 (아닐 경우 기본적으로 models.Model 을 상속받아와서 컬럼들 생성해줘야함)

AbstractUser를 상속받았기 때문에 작성하진 않아도 id, username, email, password 등 user와 관련된 컬럼들이 생성됨

#user/models.py
from django.contrib.auth.models import AbstractUser

class UserModel(AbstractUser):
    def __str__(self):
        return self.email

    class Meta:
        db_table = 'user'

Abstract하여 생성된 DB 테이블

2. user APP의 urls를 생성하여 views.py의 함수들과 연결

# user/urls.py
from django.urls import path
from user import views

urlpatterns = [
    path('user/join', views.join, name='join'),
    path('user/login', views.login, name='login'),
    path('user/logout', views.logout, name='logout'),
]

3. views.py에서 회원가입 함수 join구현

GET요청으로 올 경우 회원가입페이지 띄우게하고 POST요청으로 올 경우 json데이터로 request 받아 온 것을 로드해서

패스워드는 해싱 처리하여 저장하고 입력받은 데이터 값 유효성 검사를 진행하고 실패할 경우 2가지 출력법

1) HttpResponse로 결과값을 return해서 alert에 response로 받아온 오류 메세지 출력

2) render로 'error'값을 명시해줘서 장고내장함수(jinja와 사용법비슷)으로

html에 'error' 가 있을 경우 div를 하나 만들어줘서 오류 메세지 출력

DB 에 저장할때는 기본적인 방법도 사용가능하나 장고 내장함수를 사용하면 보다 짧게 작성할 수 있다.

3) 마지막에 UserModel.objects.create_user() 함수를 사용할 경우 username 과 password는 필수로 줘야하며

password는 자동으로 해싱처리가 된다!

# user/views.py
from django.shortcuts import render, redirect
from .models import UserModel

def join(request):
    if request.method == "GET":
        return render(request, 'user/join.html')
    elif request.method == "POST":
        email = request.POST.get('email', '')
        password = request.POST.get('password', '')
        username = request.POST.get('username', '')
        if '@' not in email:
            return render(request, 'user/join.html', {'error': '이메일 형식이 아닙니다.'})
        if len(password) < 8:
            return render(request, 'user/join.html', {'error': '비밀번호는 8자 이상입니다.'})
        if len(username) < 2:
            return render(request, 'user/join.html', {'error': '닉네임은 2자 이상입니다.'})
        if UserModel.objects.filter(email=email).exists():
            return render(request, 'user/join.html', {'error': '중복된 이메일입니다.'})
        if UserModel.objects.filter(username=username).exists():
            return render(request, 'user/join.html', {'error': '중복된 닉네임입니다.'})

        UserModel.objects.create_user(email=email, password=password, username=username)
        return redirect('login')
# templates/user/join.html
<h1>회원가입</h1>
<form method="post" action="{% url 'join' %}">
    {% csrf_token %}
    <input type="text" id="username" name="username" placeholder="닉네임">
    <input type="text" id="email" name="email" placeholder="이메일">
    <input type="password" id="password" name="password" placeholder="비밀번호">
    {% if error %}
        <p>{{ error }}</p>
    {% endif %}
    <button type="submit">회원가입</button>
</form>
<a href="{% url 'login' %}">로그인</a>

 

로그인

1. views.py에 sign_in함수 정의하여 로그인 기능 구현

로그인 인증으로 장고 내장함수를 사용하기 위해 우선 from django.contrib import auth 를 해온 후

auth.authenticate(request, 판별할 인자)로 일치하는 유저 정보 값을 가져오는데 일치하는 유저 정보 값이 있다면 auth.login함수에 인자를 줘서 로그인시키면 자동으로 세션에 로그인 정보가 저장되고 그 후 메인화면으로 redirect 시킴

* username을 필수로 있어야만 authenticate()함수를 사용할 수 가 있다.

#user/views.py
from django.shortcuts import render, redirect
from .models import UserModel
from django.contrib import auth

def login(request):
    if request.method == "GET":
        return render(request, 'user/login.html')
    elif request.method == "POST":
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')

        me = auth.authenticate(request, username=username, password=password)
        if me is not None:
            auth.login(request, me)
            return redirect('/')
        else:
            return render(request, 'user/login.html', {'error': '아이디와 패스워드를 확인해 주세요'})
# templates/user/login.html
<h1>로그인</h1>
<form method="post" action="{% url 'login' %}">
    {% csrf_token %}
    <input type="text" id="username" name="username" placeholder="사용자ID">
    <input type="password" id="password" name="password" placeholder="비밀번호">
    {% if error %}
        <p>{{ error }}</p>
    {% endif %}
    <button type="submit">로그인</button>
</form>
<a href="{% url 'join' %}">회원가입</a>

로그인 인증 

로그인이 되어있지 않을 경우 보낼 URL 지정하고 views.py에 로그인인증을 해야하는 함수에

@login_required 데코레이션을 달아주면 자동으로 인식하게 해준다.

# 프로젝트폴더/settings.py
LOGIN_URL = '/login/'

로그아웃

# user/views.py
@login_required
def logout(request):
    auth.logout(request) # 인증 되어있는 정보를 없애기
    return redirect("/")
# temaplates/nav.html
{% if user.is_authenticated %}
<a href="{% url 'logout' %}">로그아웃</a>
{% endif %}

csrf_token (Cross Site Request Pogery)

장고에서는 미들웨어에서 csrf 공격을 막기위한 보안 장치가 걸려있는데 (settings.py에서 확인가능)

form 태그 안에{% csrf_token %} 를 추가하여 버튼 타입을 submit으로 사용하면 csrf_token을 자동 생성 저장한다.

submit 아닌 button 사용하여 자바스크립트 호출 시에는 csrf_token 생성 및 저장을 위한 코드가 필요하다!

MIDDLEWARE = [
    ...
    'django.middleware.csrf.CsrfViewMiddleware',
    ...
]

이를 위해서 csrf토큰을 만들어서 저장하는 코드가 필요하다.

즉, django에서 ajax 통신을 하기 위해 js를 통해 csrf_token 생성 및 주입을 위한 아래의 js 코드 추가를 해줘야한다!

쿠키에 저장되어 있는 값을 가져와서 토큰으로 사용을 하며 ajaxSetup으로 설정으로 만들어서 통신할 때마다 매번 반복 코드를 작성하지 않도록 설정해주는 코드들이다.  

<script>
        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = cookies[i].trim();
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }

        var csrftoken = getCookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        $.ajaxSetup({
            headers: {"X-CSRFToken": '{{ csrf_token }}'}
        });
    </script>

@csrf_exempt 데코레이션을 사용해서 해당 함수가 호출될 때 csrf_token을 검사하지 않게하는 방식이 있지만 보안은 어차피 당연히 지켜져야하는 부분이기 때문에 뺄 이유 가 없다.

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
    return HttpResponse('Hello world')

자바스크립트를 통한 통신이 아닌 form 태그만으로 submit 을 처리한다면 jinja구문 처럼 간단하게  form 태그 시작하고 바로 아랫 줄에 {% csrf_token %} 만 추가해주면 토큰이 자동으로 생성된다.

<form method="post" action="...">
{% csrf_token %}
...
</form>