일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- drf
- 리그오브레전드
- Riot
- 자바
- 탐욕알고리즘
- programmers
- 장고
- github
- greedy
- 백준
- Django
- 그리디
- 내일배움캠프
- python
- API
- 그리디알고리즘
- sort
- 알고리즘
- java
- 스파르타내일배움캠프TIL
- lol
- 코딩테스트준비
- 롤
- 파이썬
- 코딩테스트
- SQL
- 라이엇
- git
- 프로그래머스
- 스파르타내일배움캠프
- Today
- Total
Lina's Toolbox
Django Auth 본문
Auth
- 웹 개발에 빼놓을 수 없는게 바로 Auth입니다!
- → 당연히 django도 제공하고 있습니다. 🙂
- settings.py를 살펴봅시다.
request → response나갈때 그냥 나가는게 아닌
미들웨어들을 거친 후 response로 나간다.
내가 만약 무조건 거치게 만들고싶은 과정이 있다면
커스텀 미들웨어를 만들어 추가해줄수 있다.
- django.contrib.auth → 인증 핵심 로직과 관련 기본 모델
- django.contrib.contenttypes → 사용자의 모델과 권한을 연결
💡 아, 그런가보다~ 하고 넘어가도 되는 부분입니다.
인증(Authentication)과 권한(Authorization)을 합쳐서 Auth라고 대개 인증시스템이라고 명명합니다.
- 인증(Authentication) : 내가 누구인지를 입증하는 것 (너 로그인 했니?)
- 권한(Authorization) : 수행할 수 있는 자격 여부 (너 권한 있니?)
쿠키(cookie)와 세션(session)
HTTP
- HTTP 이야기가 계속 나오는 이유
- 웹은 HTTP 방식을 사용해서 통신을 주고받고 있기때문입니다.
(현대의 모든 웹에서의 데이터 교환의 기본 - 우리는 그 위에서 장고를 쓰고 있는 것!)
- 웹은 HTTP 방식을 사용해서 통신을 주고받고 있기때문입니다.
- HTTP 특징
- 비연결지향 (Connectionless)
- 한 번 요청에 대한 응답을 하면 연결이 끊어짐
- 무상태(Stateless)
- 연결이 끊어지면 통신이 끝나고 서로를 잊어버림
- 모든 메세지는 독립적
(한번 보냈던 메시지는 그다음 보내는 메세지와 전혀관련이 없다. - 무상태)
- 만약 쿠키와 세션이 없다면?
- 이전의 요청을 기억하지 못하게 됩니다.
- 따라서 요청을 보낼 때 마다 매번 로그인을 해야합니다.
- 이렇게 되면 너무 불편하겠죠?
- 비연결지향 (Connectionless)
→ 로그인한 상태를 유지하려면?
→ 서로를 기억하기 위해 필요한 것이 바로 ‘쿠키와 세션’
쿠키(Cookie) 🍪
- 서버 → 웹 브라우저에 전달하는 작은 데이터 조각입니다.
- 유저가 웹을 방문하게 되면 서버로부터 쿠키를 전달받습니다.
- Key-Value 형태로 데이터가 저장됩니다.
- 이후 동일한 서버에 보내는 모든 요청에 쿠키가 함께 전달됩니다.
- 쿠키 데이터는 유저의 로컬에 저장되는 정보입니다.
- 웹브라우저에서 관리자 도구 열고, application탭에서 확인 가능!
심지어 수정, 삭제도 가능!! (ex. 장바구니 담겨있던게 사라짐)
→ 쿠키는 조작이 너무 쉽다!
→ 보안이슈
그래서 이 귀여운 쿠키가 🍪
쿠키는 문자열로 이루어진 유저의 작은 데이터조각 !
장바구니 기능 (쿠키에 어떤 물건을 담았었는지 기억)
최근 검색한 상품들(쿠키에 기억) → 서버에서 받아서 맞춤형 광고
오늘 다시보지 않기 (쿠키에 기억)
→ 어? 그럼 내가 뭐했는지 쿠키에 싹 저장해두었다가, 서버가 가져가네 …? 🤔
조금은 무서운(?) 이야기
- 이 쿠키가 활용되는 가장 중요한 분야가 바로 ‘광고시장’입니다.
- 검색 기록을 추적해서 쉽게 유저별 맞춤형 광고를 할 수 있기 때문입니다.
- 그렇기 때문에 쿠키에 대해 개인정보이슈에 대해 논의가 이루어지고 있습니다!
→ 광고 시장이 크게 바뀔 수 있는 변화
→ 구글 크롬도 쿠키에 대한 제제를 발표 (2024년 발효 예정)
💡 한 줄 정리
웹 페이지에 요청을 보내면 서버가 쿠키를 함께 전달하고
이후부터는 같은 서버에 보내는 모든 요청에 쿠키를 함께 담아서 요청을 보내게 된다.
세션(Session)
만약 쿠키 🍪만 있다면 어떤 일이 일어날까요?
쿠키에 내가 로그인한 유저다 ! 라고 적어놓고 그게 있으면 서버가 매번 로그인 한 유저라고 생각하고 데이터를 준다면 누구나 가입된 유저인 것처럼 행동할 수 있습니다.
→ 쿠키는 유저의 로컬에 저장된 단순한 문자열 정보이기에 유저가 마음대로 바꿀 수 있습니다.
💡 세션은 서버와 클라이언트(브라우저)간 '상태(State)'를 기억하기 위한 것입니다!
- 세션과 쿠키가 쓰이는 방법은 다음과 같습니다.
- 클라이언트가 서버에 접속하면
- 서버가 특정 session id(임의의 난수)를 발급하고 기억(DB나 메모리에서 기억)
- 클라이언트에 SID를 보냄. 클라이언트는 session id 전달받아 쿠키에 저장
- 이후 클라이언트는 다음 요청을 할 때, 해당 쿠키(session id)를 이용해서 요청
- 서버에서는 쿠키에서 session id를 꺼내서 검증(실제로 해당 SID있는 지 확인)
- 검증에 성공했다면 알맞은 로직을 처리
→ 로그인은 이러한 절차로 구현됩니다.
→ 세션은 쿠키를 이용한 기술!
💡 쿠키는 영원한 것은 아니고 수명이 있다!
- 쿠키의 수명
- 세션쿠키, Session Cookie
- 현재의 세션이 종료되면(브라우저가 닫히면) 삭제됩니다.
- 지속쿠키, Persistent Cookie
- 디스크에 저장되며 브라우저를 닫거나 컴퓨터를 재시작해도 남아있습니다.
- Max-Age를 지정하여 해당 기간이 지나면 삭제가 가능합니다.
- 세션쿠키, Session Cookie
Django의 Session 과 Auth
django에서 알아서 처리해주고 있기 때문에 직접 작성할 필요가 없습니다!
Django의 Authentication System
로그인 구현하기
💡 로그인은 결국 Session을 Create하는 로직이라고 할 수 있습니다!
로그인을 하려면,
→ form에서 id와 pw를 입력받고
→ DB에서 유저판독
→ 세션테이블가서
→ 난수생성
→ 세션테이블에 기억
이러한 과정이 필요함!
하지만 장고에서는 login()함수만 쓰면 이걸 다 해줌!!
Django는 이 과정을 전부 내부적으로 처리할 수 있는 기능을 제공하고 있기 때문에
우리가 이러한 session에 대한 로직을 생각하지 않아도 됩니다. (Django 👍)
우리는 가져다 쓰기만 하면 됨!
→ 우리가 프레임워크를 쓰는 이유 (생산성⬆️)
Authentication Form
- Django의 Built-in Form
- 로그인을 위한 기본적인 form을 제공합니다.
login()
- 개발자가 직접 구현하지 않아도 login()함수 하나만 사용하면 됩니다!
- 사용자 로그인 처리를 해주고 내부적으로 session을 사용해서 user 정보 저장합니다. (Django 👍)
사용해보자
- 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는 기본적으로 모든게 갖춰져 있습니다.
Django의 기본 유저 모델 저는 이것 말고도 더 많은 정보가 필요한데요 ?
→ 이 유저모델을 ‘확장’하면 되겠죠 🙂
이것은 클래스임!
커스텀하고 싶다면
이것을 상속하여 확장시킨 나만의 커스텀 유저 모델을 만들 수 있다.
그러면 회원으로 등록은 어떻게 하는건가요?
회원 가입 역시 Django가 제공하는 여러기능이 있지만 이건 다음으로!
지금은 superuser 를 하나 만들어서 진행하겠습니다.
superuser란?
createsuperuser
python manage.py createsuperuser # 슈퍼유저 생성
- Django가 제공하는 Admin 기능에 접근할 수 있는 최고 권한 유저를 말합니다.
- User / Staff / Superuser 로 구분
User : 구매자
Staff: 상품관리자
Superuser : 어드민
- 강의에서는 아래 정보로 사용하기를 추천합니다. (우리는 망각이 특기니까 까먹었을 땐 현재 페이지를 보면 알 수 있겠죠?)
- username : admin
- password : admin1234
로그인 처리를 위한 view 작성하기
accounts/views.py (2차)
# accounts/views.py
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 내부에서 처리합니다.
get으로 들어오면: 로그인 화면 보여줌
post: 로그인 처리 후 다른 페이지로 리다이렉트 해줘야함
login 임포트해줘야한다. → from django.contrib.auth import login
우리의 login함수와 이름 같으므로 as auth_login해준다.
form.get_user() 도 장고가 제공해주는 기능!
auth_login: 장고가 제공해주는 로그인 함수
request를 첫번째 인자로 받는다 (리퀘스트를 받아야 리퀘스트로 들어온 해당 브라우저의 쿠키를 받아 처리가 가능하겠죠?)
→ 한줄로 로그인 처리가 끝난다!
→ 장고의 힘 👍🏻
로그인 해보기
우리가 만든 화면에서 로그인해보면 session id가 쿠키에 생겼다!
DB에 장고 세션 테이블을 열어보면 해당 세션이 있다.
무슨값인지는 우리도 모른다. 장고가 알아서 내부적으로 암호화처리하여 어떤 유저인지의 정보가 있다.
이 밸류 값을 가지고 장고가 알아서 어떤 유저인지 확인하는 것.
이 세션 아이디를 날리면 로그아웃처리된다.
로그인 링크 달아주기 (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>
<body>안에 <div>태그 안에 넣는게 일반적임.
(나중에 css로 조절할때 이 div만 조정하면 되므로)
로그아웃 구현하기
- 로그아웃이란?
- 결국 서버의 세션 데이터를 지우는 것입니다!
(request 까서 쿠키에 세션아이디 있으면 찾아서 지우고 쿠키에서도 세션아이디 지우고...)
- 결국 서버의 세션 데이터를 지우는 것입니다!
- logout()
- login()과 마찬가지로 logout()을 사용하면 간단하게 로그아웃을 사용할 수 있습니다.
(→ 장고가 해준다!) - 현재 request에서 가져온 session을 사용하여 DB에서 삭제합니다.
- 클라이언트 쿠키에서도 삭제합니다.
- login()과 마찬가지로 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"),
]
accounts/views.py
def logout(request):
auth_logout(request)
return redirect("index")
import해주고 한줄컷
- 생각해보자그럼 어떤 방법으로 요청해야할까요? get? post?
- logout은 DB를 조작하는 요청입니다. → POST!
→ 로그아웃 버튼은 a태그가 아닌 form 으로 구현해야한다.
base.html
🧐 그런데 말입니다
현재는 url로 접근해도 로그아웃이 동작하는 문제가 있습니다.
(버튼 눌러서 로그아웃하지않아도 url에서 /logout/ 를 붙여 들어가면 로그아웃이 되어버린다.)
아, 그거 view에서 막아주면 되죠 ~ 🙂
(logout) accounts/views.py
def logout(request): if request.method == "POST": auth_logout(request) return redirect("index")
post일 때만 처리하도록 막아주었다!
→ 우리는 앞으로 많은 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 로 들어가면 아래의 화면이 나옵니다.
💡 상태코드
하이라이트 된 부분에 500이라는 숫자가 보이죠?
이것은 상태코드로 요청에 대한 서버의 응답상태를 말합니다.
상태코드는 총 5가지 종류가 있습니다.
여기서는 간단하게만 설명해드릴게요.
400번대 코드, 즉 403, 404와 같은 코드라면 클라이언트의 요청에 문제가 있음을 나타내고
500번대 코드는 서버 내부에 문제가 생겨 요청을 처리할 수 없다는 것을 나타냅니다.
존재하지 않는 게시물을 조회하려고 했기 때문에
클라이언트의 요청에 문제가 있음을 나타내는 400번대 상태코드(404 Not Fount)가 더 적절하겠죠?
(상태코드도 mdn에 검색하면 나옵니다!)
수정해봅시다!
- articles/views.py
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return redirect ("articles:articles")
이렇게 적어줘도 되겠지만,
article = get_object_or_404(Article, pk=pk) 이렇게만 수정해주면 더 편하겠죵?
참고로 사용하려면 import 해줘야함.
http요청 method 는 get, post말고도 다양하게 있다.
if request.method == "POST"
pass
elif request.method == "GET"
pass
else:
pass
이렇게 다해주려면 너무 귀찮다!
→ 데코레이터를 사용하자!
💡 View Decorators
- 여러가지 다양한 HTTP 기능을 제공하기 위한 데코레이터를 제공
(데코레이터: 함수를 한번 감싸서 실행 → 함수 전,후에 실행함) - require_http_methods()
- view 함수를 특정한 method 요청에 대해서만 허용
(인자로 get, post등 요청 종류 써주면 나머지는 알아서 튕겨내준다.)
- view 함수를 특정한 method 요청에 대해서만 허용
- require_POST()
- POST 요청만 허용
- 적용해보기
(logout) accounts/views.py
def logout(request):
if request.method == "POST":
auth_logout(request)
return redirect("index")
이제 if request.method == POST 필요없다.
지워주고 @데코레이터만 써주면됨!
- Template with Auth
- Auth 기능을 Template에서 활용해봅시다.
- template으로는 우리가 context를 넘기지 않아도 자동으로 넘어가는 context들이 존재합니다.
- request.user 도 그 중에 하나로 템플릿을 랜더링할때 현재 로그인한 사용자를 나타내는 auth.User 클래스의 인스턴스 또는 AnonymousUser 인스턴스를 request.user로 접근할 수 있습니다.
- base.html에 소소하게 적용해보기
- Auth 기능을 Template에서 활용해봅시다.
로그인이 되어있을때/ 안되어있을때 마다 다르게 화면에 출력하려면?
(ex. 로그인 했을때는 로그아웃만 보이게, 로그인안햇을때는 로그인이 보이게)
- 접근 제한하기
- 로그인도 있겠다, 로그인이 된 유저와 아닌 유저가 이용할 수 있는 기능에 접근 제한을 둘 수 있으면 좋겠죠?
- is_authenticated 속성 사용하기
- @login_required 사용하기
- 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>
request.user: 로그인한 사용자의 객체를 가지고있다. (auth.User의 인스턴스)
로그인하면 admin, 로그아웃하면 annonymous user로 보인다.
2. accounts/views.py
@require_POST
def logout(request):
if request.user.is_authenticated:
auth_logout(request)
return redirect("index")
3. articles/articles.html
로그인한 유저만 글을 쓰게 하고 싶다?
해당 뷰에 로그인한 유저만 접근하도록 하려면?
if not request.user.is_authenticated:
return redirect("accounts:login")
→ 로그인한 유저만 처리할 수 있게
→ 그런데 필요한 모든 메서드에 이걸 해주려면 귀찮을 것이다.
- → @login_required
- 이 데코레이터를 붙여준 함수는 로그인이 되었을때만 실행됨
- 로그인이 되어있지 않은 상태에서 접근하면 settings.LOGIN_URL 에 설정된 경로로 redirect 시킵니다.
- 기본 값은 /accounts/login/
(→ 장고로 유저관련 기능은 accounts앱으로 만드는것이 관행인 이유)
→ 이제 로그인 안한 상태에서, url로 articles/create하면, 로그인페이지로 이동시켜준다.
- 기본 값은 /accounts/login/
- 로그인이 되어있으면 view 로직을 실행합니다.
- 로그인 성공시 이전 페이지로 자동으로 이동합니다.
→ 여기서는, 로그인하면 인덱스 페이지로 이동한다.
→ 글작성을 하고싶었는데 인덱스로 가니 불편하다..
/accounts/login/?next=글작성페이지(/articles/create/)를 해줘야할 것 같은데
장고에서 이 기능을 제공한다!
- 쿼리스트링에 next 로 저장해줍니다.
- 별도 처리 안해주면 지정한 경로로 이동합니다.
next_url = request.GET.get("next") or(만약에 없으면) "index" - 처리해주자 !
- accounts/views.py
- 별도 처리 안해주면 지정한 경로로 이동합니다.
- 쿼리스트링에 next 로 저장해줍니다.
- accounts/login.html
아직 form의 action 때문에 아직도 인덱스로 간다. (내 사이트로 다시 요청하기때문에)
쿼리스트링 없는 /login/account로 보내기 때문에 인덱스로 갔던것.
action을 지정하지 않거나, next가 있는 현재 url을 사용하라고 비워주면됨
- 적용하기
- articles/views.py
from django.contrib.auth.decorators import login_required
- /articles/create/ 로 강제 접근을 하면
- /accounts/login/?next=/articles/create/ 로 리다이렉트됩니다.
- 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
- login_required 를 지우고 안쪽 로직에서 분리하도록 처리해서 해결할 수 있습니다.
@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 자체가 에러였던 것입니다.
'스파르타 내일 배움 캠프 AI 웹개발 과정 > Django framework' 카테고리의 다른 글
배포하기 (0) | 2024.09.04 |
---|---|
API 문서화 (0) | 2024.09.04 |
장고 기초 이해도 테스트 (1) | 2024.09.02 |
Django의 URL namespace (0) | 2024.09.02 |
Django Form (0) | 2024.08.31 |