01. Auth
02. 쿠키(cookie)와 세션(session)
03. Django의 Authentication System
01. Auth: 인증(Authentication)과 권한(Authorization)을 합쳐서 Auth
- 인증(Authentication) : 내가 누구인지를 입증하는 것
- 권한(Authorization) : 수행할 수 있는 자격 여부
02. 쿠키(cookie)와 세션(session)
- HTTP 특징
- 비연결지향 (Connectionless)
- 한 번 요청에 대한 응답을 하면 연결이 끊어짐
- 무상태(Stateless)
- 연결이 끊어지면 통신이 끝나고 서로를 잊어버림
- 모든 메세지는 독립적
- 만약 쿠키와 세션이 없다면?
- 이전의 요청을 기억하지 못하게 됨
- 따라서 요청을 보낼 때 마다 매번 로그인을 해야 함
- 비연결지향 (Connectionless)
쿠키(Cookie) 🍪: 로그인 정보나 검색 기록 등을 서버가 클라에게 보내서 다음에 요청할때 다시금 로그인 등을 하지 않게끔 하는 것
서버 → 웹 브라우저에 전달하는 작은 데이터 조각
- 유저가 웹을 방문하게 되면 서버로부터 쿠키를 전달 받음
- Key-Value 형태로 데이터가 저장
- 이후 동일한 서버에 보내는 모든 요청에 쿠키가 함께 전달
- 쿠키 데이터는 유저의 로컬에 저장되는 정보
세션(Session): 세션은 서버와 클라이언트(브라우저)간 “상태(State)”를 기억하기 위한 것
- 쿠키에 내가 로그인한 유저다! 라고 적어놓고 그게 있으면 서버가 매번 로그인 한 유저라고 생각하고 데이터를 준다면 누구나 가입된 유저인 것처럼 행동할 수 있음.
- → 쿠키는 유저의 로컬에 저장된 단순한 문자열 정보이기에 유저가 마음대로 바꿀 수 있습니다.
- 위와같은 문제 때문에 세션이 존재
- 세션과 쿠키가 쓰이는 방법
- 클라이언트가 서버에 접속하면
- 서버가 특정 session id를 발급하고 기억(임의의 난수)
- session id 전달받아 쿠키에 저장
- 이후 클라이언트는 해당 쿠키를 이용해서 요청
- 서버에서는 쿠키에서 session id를 꺼내서 검증
- 검증에 성공했다면 알맞은 로직을 처리
→ 로그인은 이러한 절차로 구현
- 쿠키의 수명
- 세션쿠키, Session Cookie
- 현재의 세션이 종료되면(브라우저가 닫히면) 삭제
- 지속쿠키, Persistent Cookie
- 디스크에 저장되며 브라우저를 닫거나 컴퓨터를 재시작해도 남아있음
- Max-Age를 지정하여 해당 기간이 지나면 삭제가 가능
- 세션쿠키, Session Cookie
Django의 Session 과 Auth
django에서 알아서 처리해주고 있기 때문에 직접 작성할 필요가 없음.
03. Django의 Authentication System:
로그인 구현하기
로그인은 결국 Session을 Create하는 로직이라고 할 수 있음.
물론 Django는 이 과정을 전부 내부적으로 처리할 수 있는 기능을 제공하고 있기 때문에 우리가 session에 대한 로직을 생각하지 않아도 됨
Authentication Form
- Django의 Built-in Form
- 로그인을 위한 기본적인 form을 제공
- accounts App을 새로 만들기.
- 계정 관련된 로직은 accounts 앱으로 하는 것이 일반적
- project App의 urls와 accounts App의 urls를 연결
- 로그인 구현하기
# accounts/urls.py
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("login/", views.login, name="login"),
]
# accounts/views.py (1차)
from django.shortcuts import render
from django.contrib.auth.forms import AuthenticationForm
def login(request):
form = AuthenticationForm()
context = {"form": form}
return render(request, "accounts/login.html", context)
# accounts/templates/accounts/login.html
{% extends "base.html" %}
{% block content %}
<h1>로그인</h1>
<form action="{% url 'accounts:login' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
{% endblock content %}
음? 그런데 로그인해보려고 생각해보니…
- 회원가입은?
- 아니 그전에 ‘회원’에 대한 정의도 한적이 없는데?
- Django는 기본적으로 모든게 갖춰져 있음. (회원가입에 대한건 다음 포스터에서)
일단 superuser 를 하나 만들어서 진행
python manage.py createsuperuser # 슈퍼유저 생성
- Django가 제공하는 Admin 기능에 접근할 수 있는 최고 권한 유저를 말함.
- User / Staff / Superuser 로 구분
- 강의에서는 아래 정보로 사용하기를 추천
- username : admin
- password : admin1234
로그인 처리를 위한 view 작성하기
# accounts/views.py (2차)
from django.shortcuts import render, redirect
from django.contrib.auth import login as auth_login
from django.contrib.auth.forms import AuthenticationForm
def login(request):
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
auth_login(request, form.get_user())
return redirect("articles:index ")
else:
form = AuthenticationForm()
context = {"form": form}
return render(request, "accounts/login.html", context)
→ session에 대한 작업은 모두 django 내부에서 처리함
로그인 해보기
로그인 링크 달아주기 (base.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="navbar">
<a href="{% url 'accounts:login'%}">로그인</a>
</div>
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
로그아웃 구현하기
- 로그아웃이란?
- 결국 서버의 세션 데이터를 지우는 것
- logout()
- login()과 마찬가지로 logout()을 사용하면 간단하게 로그아웃을 사용할 수 있음
- 현재 request에서 가져온 session을 사용하여 DB에서 삭제
- 클라이언트 쿠키에서도 삭제
# (logout) accounts/urls.py
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("login/", views.login, name="login"),
path("logout/", views.logout, name="logout"),
]
#(logout) accounts/views.py
def logout(request):
auth_logout(request)
return redirect("index")
logout은 DB를 조작하는 요청: POST사용
base.html
- 현재는 url로 접근해도 로그아웃이 동작하는 문제가 있음
(logout) accounts/views.pydef logout(request): if request.method == "POST": auth_logout(request) return redirect("index")
- 위 코드처럼 view에서 막아도 되지만 매번하기 귀찮음
HTTP 요청을 처리하는 다양한 방법
https://docs.djangoproject.com/en/4.2/topics/http/decorators/
Django가 HTTP요청을 처리하는 2가지 방법
💡 Django shortcut functions
https://docs.djangoproject.com/en/4.2/topics/http/shortcuts/#module-django.shortcuts
- render()- 템플릿을 랜더링해서 전달
- redirect() - 특정 경로로 요청을 전달
- get_object_or_404()
- get을 호출한 후 객체가 없다면 404 에러를 raise하여 404 페이지로 이동
- get_list_or_404()
- filter를 호출한 후 빈 리스트라면 404 에러를 raise하여 404페이지로 이동
- 존재하지 않는 게시글을 조회한다면
http://127.0.0.1:8000/articles/9999 로 들어가면 아래의 화면이 나옴
- 상태코드는 총 5가지 종류가 있음
400번대 코드, 즉 403, 404와 같은 코드라면 클라이언트의 요청에 문제가 있음을 나타내고
500번대 코드는 서버 내부에 문제가 생겨 요청을 처리할 수 없다는 것을 나타냄
200번대 코드는 이상 없음
존재하지 않는 게시물을 조회하려고 했기 때문에 클라이언트의 요청에 문제가 있음을 나타내는 400번대 상태코드(404 Not Fount)가 더 적절.
수정해봅시다!
View Decorators
- 여러가지 다양한 HTTP 기능을 제공하기 위한 데코레이터를 제공
- require_http_methods()
- view 함수를 특정한 method 요청에 대해서만 허용
- require_POST()
- POST 요청만 허용
- 적용해보기
- Template with Auth
- Auth 기능을 Template에서 활용
- template으로는 우리가 context를 넘기지 않아도 자동으로 넘어가는 context들이 존재
- request.user 도 그 중에 하나로 템플릿을 랜더링할때 현재 로그인한 사용자를 나타내는 auth.User 클래스의 인스턴스 또는 AnonymousUser 인스턴스를 request.user로 접근할 수 있음
- base.html에 소소하게 적용해보기
- Auth 기능을 Template에서 활용
- 접근 제한하기
- 로그인이 된 유저와 아닌 유저가 이용할 수 있는 기능에 접근 제한 두기
- is_authenticated 속성 사용하기
- @login_required 사용하기
- is_authenticated
- base.html
- 로그인이 된 유저와 아닌 유저가 이용할 수 있는 기능에 접근 제한 두기
# (is_authenticated) base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="navbar">
{% if request.user.is_authenticated %}
<h3>안녕하세요, {{ user }}님</h3>
<form action="{% url 'accounts:logout' %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
{% else %}
<a href="{% url 'accounts:login'%}">로그인</a>
{% endif %}
</div>
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
# accounts/views.py
@require_POST
def logout(request):
if request.user.is_authenticated:
auth_logout(request)
return redirect("index")
articles/articles.html
- login_required
- 로그인이 되어있지 않은 상태에서 접근하면 settings.LOGIN_URL 에 설정된 경로로 이동
- 기본 값은 /accounts/login/
- 로그인이 되어있으면 view 로직을 실행
- 로그인 성공시 이전 페이지로 자동으로 이동
- 쿼리스크링에 next 로 저장
- 별도 처리 안해주면 지정한 경로로 이동
- 쿼리스크링에 next 로 저장
- 로그인이 되어있지 않은 상태에서 접근하면 settings.LOGIN_URL 에 설정된 경로로 이동
accounts/views.py
accounts/login.html
# articles/views.py
from django.contrib.auth.decorators import login_required
- /articles/create/ 로 강제 접근을 하면
- /accounts/login/?next=/articles/create/ 로 리다이렉트 됨
- update, delete도 모두 적용해주기
# (update, delete) accounts/views.py
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
@login_required
@require_POST
def delete(request, pk):
article = Article.objects.get(pk=pk)
article.delete()
return redirect("articles:articles")
@login_required
@require_http_methods(["GET", "POST"])
def update(request, pk):
article = Article.objects.get(pk=pk)
if request.method == "POST":
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
article = form.save()
return redirect("articles:article_detail", article.pk)
else:
form = ArticleForm(instance=article)
context = {
"form": form,
"article": article,
}
return render(request, "articles/update.html", context)
def data_throw(request):
return render(request, "articles/data_throw.html")
한 번 테스트를 해보면 글 삭제시 아래와 같은 에러가 발생
- 비로그인상태에서 삭제 클릭
- 로그인 화면으로 리다이렉트
- next=<삭제 url>
- 로그인 성공
- <삭제 url>로 리다이렉트 (GET)
- 하지만 우리의 view는 GET을 허용하지 않음!
- 해결
- login_required 를 지우고 안쪽 로직에서 분리하도록 처리해서 해결 가능
# (문제 해결) accounts/views.py
@require_POST
def delete(request, pk):
if request.user.is_authenticated:
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect("articles:articles")
직접 접근하면 405 에러지만 이건 벗어난 flow에서 나오는 것.이전에 우리가 설계한 flow 자체가 에러였던 것
'Django' 카테고리의 다른 글
Django Static, Media(runserver) (1) | 2024.08.19 |
---|---|
회원기능 구현하기 (0) | 2024.08.19 |
URL Namespace / Templates Namespace (Django 플젝 시작 시 유의사항) (0) | 2024.08.16 |
Django Form, Django Model Form (0) | 2024.08.16 |
Django MTV 사용하기 (RUD) (0) | 2024.08.14 |