Authentication & Authorization
Authentication - 인증, 입증
자신이라고 주장하는 사용자가 누구인지 확인하는 행위
모든 보안 프로세스의 첫번째 단계 (가장 기본 요소)
내가 누구인지 확인하는 과정이다.
401 Unauthorized : HTTP 표준에서는 미승인(unauthorized)을 명확히 하고 있지만 의미상 이 응답은 비인증(unauthenticated)을 의미한다.
Authorization - 권한 부여, 허가
사용자에게 특정 리소스 또는 기능에 대한 액세스 권한을 부여하는 과정 (절차)
보안 환경에서 권한 부여는 인증이 먼저 필요하다. (사용자는 조직에 대한 엑세스 권한을 부여받기전에 먼저 자신의 id가 진짜인지 먼저 확인해야 한다.)
인증이 되었어도 모든 권한을 부여받는 것은 아니다.
403 Forbidden : 401과 다른 점은 서버는 클라이언트가 누구인지 알고있는 것이다.
인증 이후에 권한이 따라오는 경우가 많다. (회원가입 후 로그인시 서비스를 이용할 수 있는 권한 생성)
단, 모든 인증을 거쳐도 권한이 동일하게 부여되는 것은 아니다. (로그인을 해도 다른사람의 글 수정 삭제 불가능)
세션, 토큰, 제 3자를 활용하는 등의 다양한 인증방식이 존재한다.
DRF 공식문서에서 제안하는 인증 절차 방법
https://www.django-rest-framework.org/api-guide/authentication/
Authentication - Django REST framework
www.django-rest-framework.org
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
BasicAuthentication
SessionAuthentication
settings.py에 작성해야 한다.
기본적인 인증 절차를 어떠한 방식으로 둘 것인지를 설정하는 것이다. 예시의 2가지 방법 외에도 각 framework마다 다양한 인증 방식이 있다.
모든 상황에 대한 인증방식을 정의하는 것이므로 각 요청에 따라 다른 인증 방식을 거치고자 한다면 다른 방식이 필요하다.
view 함수마다 다른 인증 방식을 설정하고자 한다면 autehntication_classes decorator를 활용한다.
permission_classes : 권한 관련 설정, 권한 역시 view 함수마다 다른 접근 권한 요구할 수 있다.
다양한 인증 방식
TokenAuthentication
가장 기본적인 수준의 인증 방식
기본적인 보안 기능 제공
다양한 외부 패키지 있다.
SessionAuthentication
Django에서 사용했던 session 기반의 인증 시스템
DRF와 Django의 session 인증 방식은 보안적 측면을 구성하는 방법에 차이가 있다.
RemoteUserAuthentication
Django의 Remote user 방식을 사용할 때 활용하는 인증 방식
TokenAuthentication 사용방법
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],}
settings.py에서 DEFAULT_AUTHENTICATION_CLASSES를 정의한다.
인증을 토큰기반으로 사용하겠다고 하는 것이다.
INSTALLED_APPS = [
'rest_framework.authtoken',]
INSTALLED_APPS에 rest_framework.authtoken 등록
migrate 다시 해야 한다.
각 User 마다 고유 Token 생성하고 생성한 Token을 각 User에게 발급해야 한다.
User는 발급받은 Token을 요청과 함께 전송한다.
Token을 통해 user 인증, 권한 확인한다.
User는 발급 받은 Token을 headers에 담아 요청과 함께 전송한다.
반드시 Token 문자열 함께 삽입한다. 삽입해야 할 문자열은 각 인증방식마다 다르다. Token문자열과 발급받은 실제 token 사이를 공백으로 구분한다.
dj-rest-auth
https://github.com/iMerica/dj-rest-auth
GitHub - iMerica/dj-rest-auth: Authentication for Django Rest Framework
Authentication for Django Rest Framework. Contribute to iMerica/dj-rest-auth development by creating an account on GitHub.
github.com
AUTH_USER_MODEL = 'accounts.User'
시작하기 전에 auth.User를 accounts.User로 변경해야 한다.
settings.py에서 user 대체 작성한다.
auth.User로 설정된 DB를 제거한다.
$ pip install dj-rest-auth
$ pip freeze > requirements.txt
패키지 설치하고 requirements에 추가한다.
INSTALLED_APPS = [
'accounts',
'rest_framework.authtoken',
'dj_rest_auth',]
App 등록한다.
path('accounts/', include('dj_rest_auth.urls')),
url 등록한다.
accounts/로 이동하면, 만든 적 없는 url이 있다.
인증이 필요한 기능들이 dj_rest_auth로 만들어 진 것이다.
회원가입 기능은 없다. ( 회원가입은 토큰을 생성해야 한다. )
https://dj-rest-auth.readthedocs.io/en/latest/installation.html#registration-optional
Installation — dj-rest-auth 2.2.5 documentation
Using django-allauth, dj-rest-auth provides helpful class for creating social media authentication view. Note Points 1 and 2 are related to django-allauth configuration, so if you have already configured social authentication, then please go to step 3. See
dj-rest-auth.readthedocs.io
Registration 기능을 사용하기 위해 추가 기능 등록, 설치가 필요하다.
dj-rest-auth는 소셜 회원가입을 지원한다.
dj-rest-auth의 회원가입 기능을 사용하기 위해 django-allauth가 필요하다.
$ pip install 'dj-rest-auth[with_social]'
django-allauth 설치
INSTALLED_APPS = [
# registration
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration',
]
SITE_ID = 1
APP 등록 & SITE_ID 설정
SITE_ID : django는 하나의 컨텐츠를 기반으로 여러 도메인에 컨텐츠를 게시가능하도록 설계된다. 다수의 도메인이 하나의 데이터베이스에 등록된다. 현재 프로젝트가 첫번째 사이트임을 나타내는 것이다.
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'))
]
앱/urls.py 에 url 등록한다. include에 dj_rest_auth.registration.urls 등록한다.
allauth 추가에 대한 migrate 진행한다.
만들어준 url을 확인해보았을 때 get method는 접근 불가능하다는 메세지가 뜨고,
POST 요청 양식을 제공한다. (email 생략가능하다.)
Sign up & Login
회원가입 요청해본다.
요청에 대한 응답으로 Token 발급받았다.
로그인 시에도 동일한 토큰 발급받아 정상 로그인 가능하다.
발급받은 토큰은 테스트를 위해 기록한다.
password change
/accounts/password/change/
로그인 되어있거나 인증이 필요한 기능이다.
DRF 자체 제공 HTML form에서는 토큰을 입력할 수 있는 공간이 없기 때문에 Postman으로 진행한다.
Body에서 new_password1, new_password2를 적어준다.
이상태에서 SEND하면 위와 같은 메세지가 나타난다.
토큰이 매 요청마다 보내서 내가 누구인지 알려주어야 한다.
토큰은 headers에서 입력한다.
key 값은 Authorization이고 value는 Token {token} 형식에 맞춰 Token 문자열과 토큰번호를 공백으로 띄우고 같이 입력한다.
정상적으로 비밀번호변경이 되었다.
Permission setting
권한 설정 방법
https://www.django-rest-framework.org/api-guide/permissions/
Permissions - Django REST framework
www.django-rest-framework.org
권한 세부 설정
1. 모든 요청에 대해 인증을 요구하는 설정 (settings.py)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],}
2. 모든 요청에 대해 인증이 없어도 허용하는 설정 (AllowAny)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],}
설정위치는 인증 방법을 설정한 곳과 동일하다.
우선 AllowAny로 모든 요청에 대해 허용 설정했다.
# permission Decorators
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def article_list(request):
if request.method == 'GET':
...
articles/views.py
global에서 AllowAny였기때문에 인증이 필요한 view에 데코레이터를 작성했다.
articles 주소로 조회 요청할 때 로그인이 필요하다.
게시글 작성 (POST)도 동일하다.
headers에 토큰을 같이 보내준다.
정상적으로 게시글을 생성하게 된다.
정리
DRF Auth with Vue
{
path: '/signup',
name: 'SignUpView',
component: SignUpView
},
router-index.js
SignUpView 컴포넌트 router 등록
<router-link :to="{ name: 'SignUpView' }">SignUpPage</router-link> |
App.vue
https://dj-rest-auth.readthedocs.io/en/latest/api_endpoints.html#basic
Server에서 정의한 field 명을 확인한다. (username, password1, password2)
<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: {
signUp(){
}
}
}
</script>
views/SignUpView.vue
SignUpView 결과 확인
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)
}
}
메서드를 완성한다.
const payload = {
username
password1
password2
}
key, value가 같으면 이렇게 작성할 수도 있다.
signUp(context,payload){
const username = payload.username
const password1 = payload.password1
const password2 = payload.password2
axios({
method : 'post',
url : `${API_URL}/accounts/signup/`,
data : {
username,
password1,
password2,
}
})
.then((res)=>{
context.commit('SIGN_UP',res.data.key)
})
.catch(err => console.log(err))
}
},
store/index.js - actions
payload로 받은 데이터를 확인하면 response.data.key에 토큰이 저장되어 있다.
이것을 가지고 mutation호출한다.
state: {
token : null
},
mutations: {
SIGN_UP(state,token){
state.token = token
}
},
state에 token을 저장하고
mutation을 통해 state를 변화시킨다.
게시물 전체 조회와 달리, 인증 요청의 응답으로 받은 token은 매번 요청하기가 어렵다.
비밀번호를 항상 보관하고 있을 수 없기 때문에 localStorage에 token 저장을 위해 vuex-persistedstate를 활용한다.
$ npm install vuex-persistedstate
설치
import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
plugins : [
createPersistedState(),
],
plugin 등록
signup진행하면 토큰이 local storage에 저장된다.
user 인증 정보를 localStorage에 저장하는 것은 안전하지 않다.
그래서 vuex-persistedstate는 쿠키를 사용해서 관리하거나 로컬저장소를 난독화하여 관리한다.
login request
https://dj-rest-auth.readthedocs.io/en/latest/api_endpoints.html#basic
Server에서 정의한 field 명을 확인한다. (username, password)
<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: {
logIn(){
const payload = {
username : this.username,
password : this.password,
}
this.$store.dispatch('logIn',payload)
}
}
}
</script>
views/logInView.vue
actions 호출한다.
{
path: '/login',
name: 'LogInView',
component: LogInView
},
<router-link :to="{ name: 'LogInView' }">LogInPage</router-link>
라우터 등록, router-link 작성
logIn(context,payload){
axios({
method:'post',
url:`${API_URL}/accounts/login/`,
data : {
username : payload.username,
password : payload.password,
}
})
.then(res=>{
context.commit('SAVE_TOKEN',res.data.key)
})
.catch(err=>console.log(err))
}
actions
받아온 payload를 이용해서 axios로 post 요청 보내고, token(res.data.key)를 가지고 mutation을 호출한다.
SAVE_TOKEN(state,token){
state.token = token
}
mutation
signUp때도 같은 동작을 했었다. SIGN_UP mutation을 SAVE_TOKEN으로 이름을 바꾸고 같이 사용하도록 한다.
signUp action의 호출 mutation이름도 수정해주어야 한다.
정상 작동, 정상적으로 토큰이 저장된다.
IsAuthenticated in Vue
회원가입, 로그인 요청에 대한 처리 후 state에 저장된 Token을 직접확인하기 전까지 인증여부를 확인할 수 없다.
인증 되지 않았을 시 게시글 정보를 확인할 수 없는데, 로그인 여부를 확인할 수 있는 수단이 없다.
getters: {
isLogin(state){
return state.token ? true : false
}
},
store/index.js에 로그인 여부 판별 코드 getter를 작성한다.
computed:{
isLogin(){
return this.$store.getters.isLogin
}
},
created() {
this.getArticles()
},
methods: {
getArticles(){
if (this.isLogin){
this.$store.dispatch('getArticles')
} else {
alert ('로그인이 필요한 서비스 입니다.')
this.$router.push({name : 'LogInView'})
}
}
}
views/ArticleView.vue
computed에 getters.isLogin을 가져오고, getArticles()를 수정한다.
isLogin의 값에 따라서 getArticles를 수행하거나, 로그인을 수행하도록 나눈다.
import router from '../router'
SAVE_TOKEN(state,token){
state.token = token
router.push({name:'ArticleView'})
}
SAVE_TOKEN mutation 수정한다.
토큰을 저장하고, ArticleView로 이동한다.
router를 import해야 접근할 수 있다.
localStorage에서 token 삭제후 Articles 링크 클릭하면 로그인 페이지로 이동한다. 인증되지 않은 사용자를 LoginPage로 이동시켰다.
인증은 받았지만, 게시글 조회시 인증정보를 담아 보내고 있지 않다.
로그인은 했으나 django에서 로그인한것으로 인식하지 못하는 것이다.
발급받은 token을 요청으로 보내지 않았기 때문이다.
Request with Token
인증여부를 확인하기 위한 Token을 담아 요청을 보내려고 한다.
getArticles(context){
axios({
method : 'get',
url : `${API_URL}/api/v1/articles/`,
headers : {
Authorization : `Token ${context.state.token}`
}
})
.then((response)=>{
context.commit('GET_ARTICLES', response.data)
})
.catch((error)=>{
console.log(error)
})
},
store/index.js
axios 요청할 때 headers를 추가한다.
headers에 Authorization : Token 토큰값 으로 보내준다.
CreateView.vue, DetailView.vue에도 axios 요청의 headers에 Authorization 과 token을 추가한다.
정상적으로 article list, detail 확인된다.
django 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)
게시글 작성시 user정보를 포함하도록 user 요소를 추가한다.
makemigrations, migrate 진행한다.
class ArticleListSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username', read_only=True)
class Meta:
model = Article
fields = ('id', 'title', 'content', 'user', 'username')
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', )
articles/serializers.py
user는 사용자가 작성하지 않기 때문에 fileds에 추가한다.
user를 읽기 전용으로 제공한다.
username을 확인할 수 있도록 username field 정의 필요하다.
elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
articles/views.py article_list
serializer저장할때 user 지정한다.
<template>
<div>
<h1>Detail</h1>
<p>작성자 : {{article.username}}</p>
<p>글 번호 : {{ article?.id }}</p>
<p>제목 : {{ article?.title }}</p>
<p>내용 : {{ article?.content }}</p>
<p>작성시간 : {{ article?.created_at }}</p>
<p>수정시간 : {{ article?.updated_at }}</p>
</div>
</template>
views/DetailView.vue
username 정보를 추가한다.
작성자 정보가 포함되었다.
drf-spectacular
swagger
스웨거란 개발자가 REST 웹 서비스를 설계, 빌드, 문서화, 소비하는 일을 도와주는 오픈 소스 소프트웨어 프레임워크
API를 설계하고 문서화하는데 도움을 주는 라이브러리
DRF API
스웨거를 생성할 수 있도록 도움을 주는 라이브러리
drf-spectacular
https://github.com/tfranzel/drf-spectacular
GitHub - tfranzel/drf-spectacular: Sane and flexible OpenAPI 3 schema generation for Django REST framework.
Sane and flexible OpenAPI 3 schema generation for Django REST framework. - GitHub - tfranzel/drf-spectacular: Sane and flexible OpenAPI 3 schema generation for Django REST framework.
github.com
drf-spectacular
OPEN API 3.0을 지원하는 DRF API OpenAPI 생성기
pip install drf-spectacular
pip freeze > requirements.txt
설치
INSTALLED_APPS = [
'drf_spectacular',]
settings.py 등록
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Your Project API',
'DESCRIPTION': 'Your project description',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
settings.py 설정
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from django.urls import path
from . import views
urlpatterns = [
# # 필수 작성
path('schema/', SpectacularAPIView.as_view(), name='schema'),
# # optional UI
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]
articles/urls.py
URL 설정
http://127.0.0.1:8000/api/v1/swagger/
정상작동 확인한다.
'Front-end > Vue.js' 카테고리의 다른 글
서버, 클라이언트 / Vue, Django (Vue with DRF) - Read, Create (0) | 2022.11.14 |
---|---|
404 Not Found (0) | 2022.11.09 |
Articles with Vue - CRUD (0) | 2022.11.09 |
router 주소 이동 (선언적, 프로그래밍적) / Dynamic Route Matching / lazy-loading / Navigation Guard (전역 가드, 라우트 가드, 컴포넌트 가드) (0) | 2022.11.09 |
Routing , Vue Routing 시작하기 (0) | 2022.11.09 |