Vue with DRF 학습 중 백엔드(DRF)에서의 인증과 권한 처리 과정을 정리
프론트엔드(Vue)와 백엔드(DRF)는 인증 구현 시 각각 다음과 같이 역할을 나눠 맡음.
- DRF(백엔드): 사용자 정보를 검증하고, 안전한 토큰을 발급하며, 어떤 요청이 허가되는지 권한 규칙을 정한다.
- Vue(프론트엔드): 로그인 폼을 제공하고, 발급받은 토큰을 저장하며, 보호된 요청을 보낼 때 토큰을 함께 전송한다.
이 글에서는 백엔드에서의 인증·권한 과정을 집중적으로 다룬다.
인증 로직 진행을 위해 User 모델 관련 코드를 활성화한다.
# articles/models.py
class Article(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
read_only_fields 설정# articles/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('user',)
💡
read_only_fields: 사용자가 직접 수정하면 안 되는 필드. (user는 서버가 채워준다)
# articles/views.py
@api_view(['GET', 'POST'])
# @permission_classes([IsAuthenticated])
def article_list(request):
...
elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
# serializer.save()
serializer.save(user=request.user) # 요청을 보낸 user를 함께 저장
return Response(serializer.data, status=status.HTTP_201_CREATED)
db.sqlite3 삭제migrations 파일 삭제fixtures의 articles.json은 user 정보가 없으므로 loaddata 불가능⚠️ TIP: 인증 자체로는 들어오는 요청을 허용하거나 거부할 수 없으며, 단순히 요청에 사용된 자격 증명만 식별한다는 점에 유의.
(즉, "너 누구야?"까지만 담당하고, "들여보낼지 말지"는 권한이 담당한다.)
인증되지 않은 요청이 권한을 거부하는 경우 두 가지 오류 코드로 응답한다.
| 상태 코드 | 의미 | 설명 |
|---|---|---|
| 401 Unauthorized | 인증 자격 증명 없음 | 요청에 유효한 인증 자격 증명(Authentication Credentials)이 없어 사용자를 식별할 수 없음 (누구인지 증명할 자료가 없음) |
| 403 Forbidden (Permission Denied) | 권한 거부 | 서버에 요청이 전달되었지만, 권한 때문에 거절됨. 401과 달리 서버는 클라이언트가 누구인지 알고 있음 |
📌 추가 메모: 403은 CSRF 토큰이 없을 때도 발생한다.
인증 정책은 두 가지 방법으로 설정할 수 있다.
수업에서 배운 것은 function based views 기준이다.
settings.py)DEFAULT_AUTHENTICATION_CLASSES를 사용한다.SessionAuthentication과 BasicAuthentication# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
}
authentication_classes 데코레이터를 사용한다.from rest_framework.decorators import authentication_classes
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
@api_view(['GET', 'POST'])
@authentication_classes([TokenAuthentication, BasicAuthentication])
def article_list(request):
pass
💡 데코레이터: 기존 함수를 감싸 특별한 기능을 추가하는 함수.
| 인증 방식 | 설명 |
|---|---|
| BasicAuthentication | 요청마다 사용자 이름과 비밀번호를 Base64로 인코딩하여 Authorization 헤더에 담아 보내는 방식 |
| ✅ TokenAuthentication | 로그인 시 발급받은 고유한 토큰(Token)을 Authorization 헤더에 담아 요청함으로써 사용자를 인증하는 방식 |
| SessionAuthentication | Django의 기본 세션 시스템을 활용하여, 브라우저가 보내는 sessionid 쿠키를 통해 사용자를 인증하는 방식 |
| RemoteUserAuthentication | 웹 서버 등 외부 시스템이 이미 처리한 인증 결과를 신뢰하고, 전달받은 사용자 이름으로 사용자를 인증하는 방식 |
📌
TokenAuthentication은 Base64 인코딩 방식을 토큰으로 대체하여 보안성을 높인 방식이다.
| 구분 | Token | Session |
|---|---|---|
| 담기는 위치 | 헤더(Header) | 쿠키(Cookie) |
| 전송 주체 | 우리(클라이언트)가 직접 전송 | 브라우저가 전송 |
Authorization 헤더에 담아 요청함으로써 사용자를 인증하는 방식서버가 인증된 사용자에게 토큰을 발급하고, 사용자는 매 요청마다 발급받은 토큰을 요청과 함께 보내 인증 과정을 거친다.
💡 token: 인증 후 발급되는, 사용자의 신원이나 권한을 증명하는 값.
TokenAuthentication 적용 과정은 다음 3단계다.
1. 인증 클래스 설정
2. INSTALLED_APPS 추가
3. Migrate 진행
TokenAuthentication 활성화 코드 주석 해제# my_api/settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
📌
settings.py에 설정하는 것은 전역 인증 정책이다.
rest_framework.authtoken 주석 해제
# my_api/settings.py
INSTALLED_APPS = [
'articles',
'accounts',
'rest_framework',
'rest_framework.authtoken',
...,
]
$ python manage.py migrate
1. 사용자 로그인 (브라우저 → Django)
2. 사용자 확인 (Django ↔ DB)
3. Token 발급 (Django)
4. 응답 (+ Token) (Django → 브라우저)
5. 데이터 요청 (+ Token) (브라우저 → Django)
6. Token 검증 (Django)
7. 응답 (+ 요청 데이터) (Django → 브라우저)
dj-rest-auth는 django.contrib.auth를 대체하는 것이 아니라, 그 위에 만들어져 기능을 확장한다.인증 기능을 RESTful API로 제공한다.
$ pip install dj-rest-auth
# my_api/settings.py
INSTALLED_APPS = [
'articles',
'accounts',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
...
]
# my_api/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
path('accounts/', include('dj_rest_auth.urls')),
# path('accounts/signup/', include('dj_rest_auth.registration.urls')),
]
이전에 설치한 dj-rest-auth는 API 인터페이스를 제공하는 역할이고, 실제 로직 실행을 담당할 라이브러리를 추가로 설치한다.
$ pip install 'dj-rest-auth[with-social]'
# my_api/settings.py
INSTALLED_APPS = [
...,
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration',
...
]
SITE_ID = 1
💡 SITE_ID : 사이트의 주소 정보를 찾는 데 사용하며, DB의 Site 테이블에서 설정한 ID 값에 매치되는 주소를 사용한다.
INSTALLED_APPS의django.contrib.sites가django_site테이블을 자동으로 생성한다.
# my_api/settings.py
MIDDLEWARE = [
...,
'allauth.account.middleware.AccountMiddleware',
]
# my_api/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
path('accounts/', include('dj_rest_auth.urls')),
path('accounts/signup/', include('dj_rest_auth.registration.urls')),
]
$ python manage.py migrate
http://127.0.0.1:8000/accounts/ 접속 시 404 페이지에서 추가된 URL 패턴을 확인할 수 있다.
accounts/ password/reset/ [name='rest_password_reset']
accounts/ password/reset/confirm/ [name='rest_password_reset_confirm']
accounts/ login/ [name='rest_login']
accounts/ logout/ [name='rest_logout']
accounts/ user/ [name='rest_user_details']
accounts/ password/change/ [name='rest_password_change']
accounts/ signup/
http://127.0.0.1:8000/accounts/signup/http://127.0.0.1:8000/accounts/login/{"key": "발급된 토큰 값"} 형태의 Token을 받는다.이제 이 Token을 Vue에서 별도로 저장하여 매 요청마다 함께 보내야 한다.
Authorization에 Token Key 값을 넣어서 보내면 인증이 가능하다.authtoken_token 테이블에서 발급된 토큰을 확인할 수 있다.게시글 작성 과정을 통해 Token 사용 방법을 익힌다. (Postman 활용)
Authorization HTTP Header에 포함시킨다.Token이 와야 하며, 공백으로 두 문자열을 구분해야 한다.Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
⚠️
Token과 토큰 값 사이의 공백은 필수다.
Postman → Headers → Key:Authorization, Value:Token 토큰값
권한 정책도 인증과 마찬가지로 두 가지 방법으로 설정한다.
DEFAULT_PERMISSION_CLASSES를 사용한다.rest_framework.permissions.AllowAny# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
permission_classes 데코레이터를 사용한다.from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
pass
💡 권한(permissions) : 요청에 대한 접근 허용 또는 거부 여부를 결정한다.
| 권한 클래스 | 설명 |
|---|---|
| IsAuthenticated | 인증된(로그인한) 사용자만 접근을 허용 |
| IsAdminUser | 스태프 권한(is_staff=True)을 가진 관리자 사용자만 접근을 허용 |
| IsAuthenticatedOrReadOnly | 인증된 사용자는 모든 요청(읽기/쓰기)을 허용하고, 비인증 사용자는 읽기 전용 요청만 허용 |
| AllowAny | 아무런 제한 없이 모든 사용자의 접근을 허용 |
request.user가 존재하고 인증된 상태인지 확인 → 즉, request.user가 anonymous user가 아닌지 확인is_staff=True) 권한을 가진 사용자만 접근을 허용하는 권한 클래스request.user.is_staff 속성 값이 True인지 확인하여 권한을 검사GET, HEAD, OPTIONS와 같은 안전한 메서드일 경우 무조건 허용하고, 그 외 메서드(POST, PUT 등)는 사용자의 인증 여부를 확인DEFAULT_PERMISSION_CLASSES 주석 해제AllowAny)# my_api/settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
# Permission
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
}
상황: 만약 관리자만 전체 게시글 조회가 가능한 권한이 설정되었을 때, 인증된 일반 사용자가 조회 요청을 하면 어떻게 되는지 응답을 확인한다.
IsAdminUser로 변경# articles/views.py
from rest_framework.permissions import IsAuthenticated, IsAdminUser
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated]) # 테스트 시 IsAdminUser 로 변경
def article_list(request):
pass
http://127.0.0.1:8000/api/v1/articles/IsAdminUser 삭제 후 IsAuthenticated 권한으로 복구# articles/views.py
from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
pass
Authorization: Token <값> 형태로 (공백 구분) 매 요청마다 보내야 한다.