DRF Auth with Vue
vue server ์์ฒญ ์ ์ ์๋ ์ฌ๋ถ ํ์ธ
โ 401 status code
โ ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์์ด๋ฏ๋ก ์กฐํ ์์ฒญ์ด ๋ถ๊ฐ๋ฅ!
SignUp Request
SignUp Page
views/SignUpView.vue
// views/SignUpView.vue
<template>
<div>
<h1>Sign Up Page</h1>
<form @submit.prevent="signUp">
<label for="username">username : </label>
<input type="text" id="username" v-model="username"><br>
<label for="password1"> password : </label>
<input type="password" id="password1" v-model="password1"><br>
<label for="password2"> password confirmation : </label>
<input type="password" id="password2" v-model="password2">
<input type="submit" value="SignUp">
</form>
</div>
</template>
<script>
export default {
name: 'SignUpView',
data() {
return{
username: null,
password1: null,
password2: null,
}
},
methods: {
}
}
</script>
โ Server์์ ์ ์ํ field๋ช ํ์ธ
- username
- password1
- password2
- router/index.js
// router/index.js
import SignUpView from '@/views/SignUpView'
Vue.use(VueRouter)
const routes = [
...
{
path: '/signup',
name: 'SignUpView',
component: SignUpView
},
...
]
src/App.vue
// src/App.vue
<template>
<div id="app">
<nav>
<router-link :to="{ name: 'ArticleView' }">Articles</router-link> |
<router-link :to="{ name: 'SignUpView' }">SignUpPage</router-link> |
</nav>
<router-view/>
</div>
</template>
- ๊ฒฐ๊ณผ ํ์ธ
SignUp Request
โ ํ์๊ฐ์ ์ ์๋ฃ ์ ์๋ต ๋ฐ์ ์ ๋ณด Token์ store์์ ๊ด๋ฆฌํ ์ ์๋๋ก actions๋ฅผ ํ์ฉํ์ฌ ์์ฒ ํ, state์ ์ ์ฅํ ๋ก์ง ์์ฑ
- ํ์๊ฐ์ ์ด๋ ๋ก๊ทธ์ธ ํ ์ป์ ์ ์๋ Toekn์ server์ ๊ตฌ์ฑ๋ฐฉ์์ ๋ฐ๋ผ ๋งค ์์ฒญ๋ง๋ค ์๊ตฌํ ์ ์์ผ๋ฏ๋ก, ๋ค์ํ ์ปดํฌ๋ํธ์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๋๋ก ์ค์ ์ํ ์ ์ฅ์์ธ vuex์์ ๊ด๋ฆฌ
views/SignUpView.vue
// views/SignUpView.vue
<script>
export default {
...
methods: {
signUp() {
const username = this.username
const password1 = this.password1
const password2 = this.password2
const payload = {
username: username,
password1: password1,
password2: password2,
}
this.$store.dispatch('signUp', payload)
}
}
}
</script>
โ ์ฌ์ฉ์ ์ ๋ ฅ ๊ฐ์ ํ๋์ ๊ฐ์ฒด payload์ ๋ด์ ์ ๋ฌ
store/index.js
// store/index.js
actions: {
signUp(context, payload) {
axios({
method: 'post',
url: `${API_URL}/accounts/signup/`,
data: {
// ์ถ์ฝํ ํํ
// username,
// password1,
// password2,
username: payload.username,
password1: payload.password1,
password2: payload.password2,
}
})
.then((res) => {
console.log(res)
context.commit('SIGN_UP', res.data.key)
})
.catch((err) => {
console.log(err)
})
}
},
โ payload๊ฐ ๊ฐ์ง ๊ฐ ๊ฐ๊ฐ ํ ๋น
โ AJAX ์์ฒญ์ผ๋ก ์๋ต ๋ฐ์ ๋ฐ์ดํฐ๋ ๋ค์์ ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
โ state์ ์ ์ฅ
store/index.js
// store/index.js
export default new Vuex.Store({
state: {
articles: [],
toekn: null,
},
getters: {
},
mutations: {
GET_ARTICLES(state, articles) {
state.articles=articles
},
SIGN_UP(state, token){
state.token = token
}
},
...
})
โ token์ ์ ์ฅํ ์์น ํ์ธ
โ mutations์ ํตํด state ๋ณํ
- ์์ฒญ ๊ฒฐ๊ณผ ํ์ธ
ํ ํฐ ๊ด๋ฆฌ
โ ๊ฒ์๋ฌผ ์ ์ฒด ์กฐํ์ ๋ฌ๋ฆฌ, ์ธ์ฆ ์์ฒญ์ ์๋ต์ผ๋ก ๋ฐ์ Token์ ๋งค๋ฒ ์์ฒญํ๊ธฐ ํ๋ค๋ค.
โ localStorage์ Token ์ ์ฅ์ ์ํด vuex-persistedstate
ํ์ฉ
- ์ค์น
$ npm install vuex-persistedstate
store/index.js
// store/index.js
import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
plugins: [
createPersistedState(),
],
...
})
- ๊ฒฐ๊ณผ ํ์ธ
[์ฐธ๊ณ ] User ์ธ์ฆ ์ ๋ณด๋ฅผ localStorage์ ์ ์ฅํด๋ ๋๋๊ฐ?
โ ๋ณด์์ ๊ด์ ์์ ์์ ํ ๋ฐฉ๋ฒ์ ์๋๋ค.
โ ๋ฐ๋ผ์ vuex-persistedstate๋ ์๋ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
- ์ฟ ํค ์ฌ์ฉํ์ฌ ๊ด๋ฆฌ
- ๋ก์ปฌ ์ ์ฅ์๋ฅผ ๋๋ ํ ํ์ฌ ๊ด๋ฆฌ
Login Request
Login Page
views./LogInView.vue
// views/LogInView.vue
<template>
<div>
<h1>LogIn Page</h1>
<form @submit.prevent="logIn">
<label for="username">username : </label>
<input type="text" id="username" v-model="username"><br>
<label for="password"> password : </label>
<input type="password" id="password" v-model="password"><br>
<input type="submit" value="logIn">
</form>
</div>
</template>
<script>
export default {
name: 'LogInView',
data() {
return{
username: null,
password: null
}
},
methods: {
}
}
</script>
router/index.js
// router/index.js
...
import LogInView from '@/views/LogInView'
Vue.use(VueRouter)
const routes = [
...
{
path: '/login',
name: 'LogInView',
component: LogInView
},
...
]
src/App.vue
// src/App.vue
<template>
<div id="app">
<nav>
<router-link :to="{ name: 'ArticleView' }">Articles</router-link> |
<router-link :to="{ name: 'SignUpView' }">SignUpPage</router-link> |
<router-link :to="{ name: 'LogInView' }">LogInPage</router-link>
</nav>
<router-view/>
</div>
</template>
- ๊ฒฐ๊ณผ ํ์ธ
Login Request
โ signUp๊ณผ ๋ก์ง์ ๋์ผํ๋ค.
โ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๋ฐ์ Token์ state์ ์ ์ฅ
- mutations๊ฐ ์ฒ๋ฆฌํ ์ ๋ฌด๊ฐ ๋์ผ
- SIGN_UP mutations๋ฅผ SAVE_TOKEN mutations๋ก ๋์ฒด ๊ฐ๋ฅ
views/LogInView.vue
// views/LogInView.vue
<script>
export default {
name: 'LogInView',
data() {
return{
username: null,
password: null
}
},
methods: {
logIn() {
const username = this.username
const password = this.password
const payload = {
username, password
}
this.$store.dispatch('logIn', payload)
}
}
}
</script>
store/index.js
actions
// store/index.js
actions: {
...
logIn(context, payload) {
const username = payload.username
const password = payload.password
axios({
method: 'post',
url: `${API_URL}/accounts/login/`,
data: {
username, password
}
})
.then((res) => {
console.log(res.data.key)
context.commit('SAVE_TOKEN', res.data.key)
})
.catch((err) => {
console.log(err)
})
}
},
store/index.js
mutations
// store/index.js
mutations: {
...
SAVE_TOKEN(state, token){
state.token = token
}
},
โ signUp์ด ํธ์ถํ mutations๋ ํจ๊ป ๋ณ๊ฒฝ
- ๊ฒฐ๊ณผ ํ์ธ
isAuthenticated in Vue
โ ํ์ ๊ฐ์
, ๋ก๊ทธ์ธ ์์ฒญ์ ๋ํ ์ฒ๋ฆฌ ํ state์ ์ ์ฅ๋ Token์ ์ง์ ํ์ธํ๊ธฐ ์ ๊น์ง๋ ์ธ์ฆ ์ฌ๋ถ ํ์ธ ๋ถ๊ฐ
โ ์ธ์ฆ๋์ง ์์์ ์ ๊ฒ์๊ธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ผ๋ ์ด์ ๋ฅผ ์ ์ ์๋ค. -> ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ ์๋จ x
store/index.js
// store/index.js
export default new Vuex.Store({
...
getters: {
isLogin(state) {
return state.token ? true:false
}
},
...
})
โ ๋ก๊ทธ์ธ ์ฌ๋ถ ํ๋ณ ์ฝ๋ ํ์ธ: Toekn์ด ์์ผ๋ฉด true, ์์ผ๋ฉด false ๋ฐํ
views/ArticleView.vue
// views/ArticleView.vue
<script>
import ArticleList from '@/components/ArticleList'
export default {
...
methods: {
getArticles() {
if (this.isLogin) {
this.$store.dispatch('getArticles')
} else {
alert('๋ก๊ทธ์ธ์ด ํ์ํ ์๋น์ค์
๋๋ค.')
this.$router.push({ name: 'LogInView'})
}
}
}
}
</script>
store/index.js
// store/index.js
import router from '@/router'
export default new Vuex.Store({
...
mutations: {
...
SAVE_TOKEN(state, token){
state.token = token
router.push({name:'ArticleView'})
}
},
โ router importํ๊ธฐ!
- ๊ฒฐ๊ณผ ํ์ธ
โ localStorage์์ token ์ญ์ ํ ์๋ก ๊ณ ์นจ
โ Articles ๋งํฌ ํด๋ฆญ ์ LogInPage๋ก ์ด๋
ํ์ง๋ง ์ฌ์ ํ...
โ ์ธ์ฆ์ ๋ฐ์์ง๋ง ๊ฒ์๊ธ ์กฐํ ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด์์ ๋ณด๋ด๊ณ ์์ง ์๋ค!
โ ๋ฐ๊ธ ๋ฐ์ token์ ์์ฒญ์ผ๋ก ๋ณด๋ด์ง ์์์ ๋ก๊ทธ์ธ์ ํ์ผ๋ Django์์๋ ๋ก๊ทธ์ธํ ๊ฒ์ผ๋ก ์ธ์ํ์ง ๋ชปํ๋ค.
Request with Token
โ ์ธ์ฆ ์ฌ๋ถ ํ์ธ์ ์ํ Token์ด ์ค๋น ๋์์ผ๋ headers HTTP์ token์ ๋ด์ ์์ฒญ์ ๋ณด๋ด๋ฉด ๋๋ค!
Article List Read with Token
store/index.js
// store/index.js
export default new Vuex.Store({
...
actions: {
getArticles(context) {
axios({
method: 'get',
url: `${API_URL}/api/v1/articles/`,
headers: {
Authorization: `Token ${ context.state.token }`
}
})
.then((res) => {
// console.log(res, context)
context.commit('GET_ARTICLES', res.data)
})
.catch((err) => {
console.log(err)
})
},
...
})
โ headers์ Authorizations์ token ์ถ๊ฐ
Article Create with Token
views/CreateView.vue
// views/CreateView.vue
<script>
...
export default {
...
methods: {
createArticle() {
...
axios({
method: "post",
url: `${API_URL}/api/v1/articles/`,
headers: {
Authorization: `Token ${ this.$store.state.token }`
},
data: {
title,
content,
},
})
.then(() => {
this.$router.push({ name: "ArticleView" });
})
.catch((err) => {
console.log(err);
});
},
},
};
</script>
Create Article with User
articles/models.py
# articles/models.py from django.db import models from django.conf import settings # Create your models here. 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)
โ ๊ฒ์๊ธ ์์ฑ์ User ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ์์ฑํ๋๋ก ์์ -> User ์ ๋ณด๋ฅผ Vue์์๋ ํ์ธ ๊ฐ๋ฅํ๋๋ก ์ ๋ณด ์ ๊ณต
- makemigrations, migrate
โ ๊ธฐ์กด ๊ฒ์๊ธ์ ๋ํ User์ ๋ณด defualt ๊ฐ ์ค์
articles/serializers.py
# articles/serializers.py
from rest_framework import serializers
from .models import Article, Comment
class ArticleListSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', read_only=True)
class Meta:
model = Article
fields = ('id', 'title', 'content')
fields = ('id', 'title', 'content', 'user', 'username')
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ('article',)
class ArticleSerializer(serializers.ModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
comment_count = serializers.IntegerField(source='comment_set.count', read_only=True)
username = serializers.CharField(source='user.username', read_only=True)
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('user', )
โ ArticleListSerializer์์ user๋ ์ฌ์ฉ์๊ฐ ์์ฑํ์ง ์๋๋ค -> fields์ ์ถ๊ฐ
โ ArticleSerializer์์ user๋ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ ๊ณต
โ username์ ํ์ธ ํ ์ ์๋๋ก username field ์ ์ ํ์
articles/views.py
# articles/views.py
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
if request.method == 'GET':
# articles = Article.objects.all()
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)
components/ArticleListItem.vue
// compoents/ArticleListItem.vue
<template>
<div>
<h5>{{ article.id }}</h5>
<p>์์ฑ์: {{ article.username }}</p>
<p>{{ article.title }}</p>
...
<hr>
</div>
</template>
'โญ Personal_Study > Vue' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
drf-spectacular (0) | 2022.11.24 |
---|---|
DFR Auth System (0) | 2022.11.22 |
Vue with DRF (0) | 2022.11.21 |
Cross - Origin Resource Sharing (CORS) (0) | 2022.11.20 |
Vue with DRF: Server & Client (0) | 2022.11.20 |
๋๊ธ