# articles/models.py
from django.db import models
from django.conf import settingsclass 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)
```
serializers의 read_only_fileds 주석 해제
# articles/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('user',)
article_list view 함수에서 게시글 생성 시 user 정보도 저장될 수 있도록 주석 해제
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
if request.method == 'GET':
articles = get_list_or_404(Article)
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
# serializer.save()
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
DB 초기화
Migration 과정 재 진행
수신된 요청을 해당 요청의 사용자 또는 자격증명과 연결하는 메커니즘
→ 누구인지를 확인하는 과정
요청에 대한 접근 허용 또는 거부 여부를 결정
Default_Authentication_classes를 사용
사용 예시
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
pass→ 서버가 인증된 사용자에게 토큰을 발급, 사용자는 매 요청마다 발급받은 토큰을 요청과 함께 보내 인증 과정을 거침
Token Authentication 활성화 코드 주석 해제
기본적으로 모든 View함수가 토큰 기반 인증이 진행될 수 있도록 설정
# settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
INSTALLED_APPS = [
'articles',
'accounts',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
'corsheaders',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.conf import settings@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
```
# settings.py
INSTALLED_APPS = [
'articles',
'accounts',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
...,
]urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
path('accounts/', include('dj_rest_auth.urls')),
]패키지 추가 설치
추가 App 주석 해제
INSTALLED_APPS = [
...,
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration',
'django.contrib.admin',
...,
]
SITE_ID = 1
관련설정 코드
ACCOUNT_EMAIL_VERIFICATION = 'none'
MIDDLEWARE = [
...,
'allauth.account.middleware.AccountMiddleware',
]
추가 URL 주석 해제
from django.contrib import admin
from django.urls import path, include
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')),
]
```
DEFAULT_PERMISSION_CLASSES를 사용
사용 예시
# settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
지정하지 않을 경우 기본적으로 이 설정은 무제한 액세스를 허용
# settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
permission_classes 데코레이터를 사용
사용 예시
# articles/views.py
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
DEFAULT_PERMISSION_CLASSES 주석 해제
기본적으로 모든 VIew 함수에 대한 접근 허용
# settings.py
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
# permission
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
}
permissio_classes관련 코드 주석 해제
전체 게시글 조회 및 생성시에만 인증된 사용자만 진행할 수 있도록 권한 설정
# articles/views.py
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
만약 관리자만 전체 게시글 조회가 가능한 권한이 설정 되었을 때, 인증된 일반 사용자가 조회 요청을 할 경우 어떻게 되는지 응답 확인하기
테스트를 위해 임시로 관리자 관련 권한 클래스 IsAdminUser로 변경
from rest_framework.permissions import IsAdminUser
@api_view(['GET', 'POST'])
@permission_classes([IsAdminUser])
def article_list(request):
pass
전체 게시글 조회 요청
403 Forbidden 응답 확인
IsAdminUser삭제 후 IsAuthenticated 권한으로 복구
from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
pass
SignupView route관련 코드 주석 해제
import SignUpView from '@/views/SignUpView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/signup',
name: 'SignUpView',
component: SignUpView
}
]
})
app컴포넌트에 SignupView 컴포넌트로 이동하는 RouterLink 작성
<template>
<header>
<nav>
<RouterLink :to="{ name: 'ArticleView' }">Articles</RouterLink> |
<RouterLink :to="{ name: 'SignUpView' }">회원가입</RouterLink> |
</nav>
</header>
<RouterView />
</template>
회원가입 form작성
<template>
<div>
<h1>회원가입</h1>
<form @submit.prevent="signUp">
<div>
<label for="username">username : </label>
<input type="text" v-model.trim="username" id="username">
</div>
<div>
<label for="password1">password : </label>
<input type="password" v-model.trim="password1" id="password1">
</div>
<div>
<label for="password2">password confirmation : </label>
<input type="password" v-model.trim="password2" id="password2">
</div>
<input type="submit">
</form>
</div>
</template>
사용자 입력 데이터와 비인딩될 반응형 변수 작성
import { ref } from 'vue'
import { useCounterStore } from '@/stores/counter'
const username = ref(null)
const password1 = ref(null)
const password2 = ref(null)
SignUpView 컴포넌트 출력 확인
회원 가입 요청을 보내기 위한 signUp함수가 해야 할 일
사용자 입력 데이터를 받아
서버로 회원가입 요청을 보냄
export const useCounterStore = defineStore('counter', () => {
const signUp = function () {
...
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
```
컴포넌트에 사용자 입력 데이터 저장 후 store의 signUp함수를 호출하는 함수 작성
const signUp = function () {
const payload = {
username: username.value,
password1: password1.value,
password2: password2.value
}
store.signUp(payload)
}
실제 회원 가입 요청을 보내는 store의 signUp함수 작성
const signUp = function (payload) {
// 1. 사용자 입력 데이터를 받아
const username = payload.username
const password1 = payload.password1
const password2 = payload.password2
// const { username, password1, password2 } = payload
// 2. axios로 django에 요청을 보냄
axios({
method: 'post',
url: `${API_URL}/accounts/signup/`,
data: {
username, password1, password2
}
})
.then((response) => {
console.log('회원가입 성공!')
const password = password1
logIn({ username, password })
})
.catch((error) => {
console.log(error)
})
}
76page
LogInView route
import LogInView from '@/views/LogInView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'LogInView',
component: LogInView
}
]
})
App 컴포넌트에 LogInView컴포넌트로 이동하는 RotuerLink 작성

로그인 form작성
<template>
<header>
<nav>
<RouterLink :to="{ name: 'LogInView' }">로그인</RouterLink>
</nav>
</header>
<RouterView />
</template>
사용자 입력 데이터와 바인딩 될 반응형 변수 작성
<script setup>
import { ref } from 'vue'
import { useCounterStore } from '@/stores/counter'
const username = ref(null)
const password = ref(null)
</script>
로그인 요청을 보내기 위한 logIn함수가 해야 할 일
사용자 입력 데이터를 받아
서버로 로그인 요청 및 응답 받은 토큰 저장
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
import { useRouter } from 'vue-router'
export const useCounterStore = defineStore('counter', () => {
const logIn = function (payload) {
...
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
컴포넌트에 사용자 입력 데이터를 저장 후 store의 logIn함수를 호출하는 함수 작성
실제 로그인 요청을 보내는 store의 logIn함수 작성
const logIn = function (payload) {
// 1. 사용자 입력 데이터를 받아
const { username, password } = payload
// 2. axios로 django에 요청을 보냄
axios({
method: 'post',
url: `${API_URL}/accounts/login/`,
data: {
username, password
}
})
.then((response) => {
// console.log('로그인 성공!')
// console.log(response)
// console.log(response.data.key)
// 3. 로그인 성공 후 응답 받은 토큰을 저장
token.value = response.data.key
router.push({ name : 'ArticleView' })
})
.catch((error) => {
console.log(error)
})
}
로그인 테스트
응답 객체 안에 Django가 발급한 Token이 함께 온 것을 확인
반응형 변수 token 선언 및 토큰 저장
export const useCounterStore = defineStore('counter', () => {
const articles = ref([])
const API_URL = 'http://127.0.0.1:8000'
const token = ref(null)
const logIn = function (payload) {
...
token.value = response.data.key
router.push({ name : 'ArticleView' })
.catch((error) => {
console.log(error)
})
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
다시 로그인 요청 후 sotre에 저장된 토큰 확인
게시글 전체 목록 조회 요청 함수 getArticles에 token추가
export const useCounterStore = defineStore('counter', () => {
const getArticles = function () {
axios({
method: 'get',
url: `${API_URL}/api/v1/articles/`,
)
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
```
401상태 코드가 사라지고 게시글이 정상적으로 출력되는 것을 확인
게시글 전체 목록 조회 요청 함수 getArticles에 token 추가
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
import { useRouter } from 'vue-router'
export const useCounterStore = defineStore('counter', () => {
const getArticles = function () {
axios({
method: 'get',
url: `${API_URL}/api/v1/articles/`,
headers: {
Authorization: `Token ${token.value}`
}
})
.then(response => {
articles.value = response.data
})
.catch(error => {
console.log(error)
})
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
```
const isLogin = computed(()=>{
if (token.value == null){
return false
} else {
return true
}
})
import { useCounterStore } from '@/stores/counter'router.beforeEach((to, from) => {
const store = useCounterStore()
// 인증된 사용자는 회원가입과 로그인 페이지에 접근 할 수 없음
if ((to.name === 'SignUpView' || to.name === 'LogInView') && (store.isLogin === true)) {
window.alert('이미 로그인 했습니다.')
return { name: 'ArticleView' }
}
})
```
import { useCounterStore } from '@/stores/counter'router.beforeEach((to, from) => {
const store = useCounterStore()
// 인증되지 않은 사용자는 메인 페이지에 접근 할 수 없음
if (to.name === 'ArticleView' && store.isLogin === false) {
window.alert('로그인이 필요해요!!')
return { name: 'LogInView' }
}
// 인증된 사용자는 회원가입과 로그인 페이지에 접근 할 수 없음
if ((to.name === 'SignUpView' || to.name === 'LogInView') && (store.isLogin === true)) {
window.alert('이미 로그인 했습니다.')
return { name: 'ArticleView' }
}
})
```
export const useCounterStore = defineStore('counter', () => {
const articles = ref([])
const API_URL = 'http://127.0.0.1:8000'
const token = ref(null)
const logIn = function (payload) {
// 1. 사용자 입력 데이터를 받아
const { username, password } = payload
// 2. axios로 django에 요청을 보냄
axios({
method: 'post',
url: `${API_URL}/accounts/login/`,
data: {
username, password
}
})
.then((response) => {
token.value = response.data.key
router.push({ name : 'ArticleView' })
})
.catch((error) => {
console.log(error)
})
}
return { articles, API_URL, getArticles, signUp, logIn, token, isLogin }
}, { persist: true })
const signUp = function (payload) {
...
.then((response) => {
console.log('회원가입 성공!')
const password = password1
logIn({ username, password })
})
.catch((error) => {
console.log(error)
})
}
애플리케이션의 설정이나 동작을 제어하기 위해 사용되는 변수
로그인 관련 User모델 및 시리얼라이저 생성
RestFramework Authentcation 설정
Dj-Rest-Auth 라이브러리
권한 정책 설정