이전 포스팅에서 초기 세팅과 로그인 화면구성만 완료했다. 회원가입도 간단하게 구현하고자
sign up 버튼을 만들고 버튼을 누르면 기본 양식이 보이지 않게하고, 회원가입 창이 뜨게 만들었다.
Vuex를 활용해서 프론트엔드의 데이터를 처리할 예정이다. 우선 장고의 특정 엔드포인트에 회원가입 데이터인 id,pw를 백엔드로 전송하기 위해서 백엔드에서 몇 가지 작업을 해야한다.
모델 정의: 회원가입 정보를 저장할 모델을 정의한다.
시리얼라이저 생성: 모델에 대한 시리얼라이저를 만든다.
뷰 작성: 회원가입을 처리하는 API 뷰를 작성합니다. 이 뷰는 사용자가 제출한 회원가입 데이터를 처리하고, 데이터를 검증하고 모델에 저장하는 역할을 한다.
URL 연결: API 뷰를 프로젝트의 URL에 연결한다.
class User(models.Model):
userId = models.CharField(max_length=100,
unique=True,
primary_key=True)
userPassword = models.CharField(max_length=100)
def __str__(self):
return self.userId
다음과 같이 User 모델을 만든다. 분명히 모델과 데이터베이스는 다르다. 모델은 데이터베이스라는 창고에 어떻게 저장할지를 정하는 설계도 같은 것이다.
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['userId', 'userPassword']
# 프론트엔드에서 전달되는 변수명과 잘 일치시켜야 함.
우리는 post 작업을 정의하고있다. 프론트에서 백엔드로, 정확히는 데이터베이스까지 저장되려면 일반적인 과정이 존재한다. 우선은 프론트엔드에서 axios통신을 통해 백엔드로 데이터가 엔드포인트로 들어온다. 이 작업은 django의 views.py와 프론트엔드 Vue.js의 axios를 활용한 코드로 완성된다. 들어온 데이터는 시리얼라이저를 통해 직렬화(ordered dict)의 통일된 형식으로 재구성된다. 그리고나서 모델이라는 통로를 통해 데이터베이스에 저장되어진다.
모델을 정의했다면 우리는 창고(데이터베이스)에 새로운 저장설계도가 생성되었음을 알려야 한다. 그것을 위해 앞선 포스팅에서 진행했던, 마이그레이션 작업을 진행하게 된다. python manage.py makemigration 명령어(마이그레이션 파일 생성-설계도 생성)와 python manage.py migrate명령어(마이그레이션 적용-설계도 창고에 알리기)로 완료할 수 있다.
정상적으로 완료되었다면 api.0001_initial...이란 마이그레이션 파일이 생성되고 이것이 DB에 적용되는 것을 볼 수 있다.
# views.py
class SignUpView(APIView):
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
장고 어플리케이션의 views.py에 다음과 같은 코드를 작성한다. 이 클래스는 프론트엔드와의 HTTP통신을 가능하게 해준다.(APIView 상속을 통해) post작업을 정의한다. request라는 매개변수는 말 그대로 프론트엔드의 데이터이다. serializer 변수에 미리 작성해둔 시리얼라이저를 호출해서 data매개변수에 request.data, 즉 회원가입 데이터에 해당하는 id와 pw를 받는다.
이 시리얼라이저의 is_valid()메서드는 연결된 model과 소통하여 타당성을 검증한다. 만약 검증이 성공했다면 .save()로 하여금 모델을 통해 데이터베이스에 데이터들이 안착하게 된다. 그리고 리턴으로 상태값을 반환한다.
그리고 추가적으로 rest_framework사용을 위해 setting.py의 installedapp부분에 "rest_framework"를 추가해준다.
SignUpSubmit() {
console.log("회원가입시도중");
const userSignUpData = {
userId: this.userId,
userPassword: this.userPassword,
};
axios
.post("http://localhost:8000/api/signup/", userSignUpData)
.then((response) => {
console.log("회원가입 성공:", response.data);
this.isSignUpSuccessful = true;
// 성공한 경우 처리
})
.catch((error) => {
console.error("회원가입 실패:", error.response.data);
this.signUpError = true;
// 실패한 경우 처리
});
},
일단 post작업만 정의하는 것이니 axios의 내부메서드인 post에 매개변수로 백엔드 엔드포인트와 회원가입데이터를 넘겨준다. .then은 요청 성공시, .catch는 요청 실패시 처리하는 내부 메서드이다. 이렇게 http통신으로 백엔드로 데이터가 post되면, 장고의 views에서 정의한 클래스의 post메서드의 request로 데이터가 들어온다 데이터는 request.data에 담겨있으며 이것은 바로 시리얼라이저로 들어가 직렬화된 후 모델을 통해 데이터베이스에 안착한다. 이것이 모두 성공한다면 데이터베이스 테이블에 데이터 행이 생성될 것이며, 브라우저 console에 성공관련한 로그들이 찍힐 것이다.
더 구체적으로 만들려면, 회원가입시 여러 제약을 추가해 아이디나 비밀번호 만들시에 일반적으로 사용되는 규칙들을 미리 적용하고, 만약 아이디 중복검사를 위해 중복검사통신 규칙을 추가해야한다. 일단은 귀찮으니 패스한다.
로그인 과정
로그인 과정은 토큰방식과 세션방식으로 나뉜다. 이용자가 많은, 확장성을 고려한다면 토큰방식이 좋다. 토큰 방식은 로그인 유지에 관련한 토큰을 클라이언트 각자가 소지하면서 클라이언트와 서버간의 독립성을 유지한다. 반면 세션방식은 서버가 이용자들의 세션을 유지시켜주며 이용자들의 행동을 추적가능하다. 반면 이용자가 많아질 경우 서버도 그만큼 부하를 받게되므로 목적에 맞게 방식을 고르면 될 듯 하다.
django에서는 인증관련한 편리한 툴을 지원한다. 이를 활용하면 굳이 시리얼라이저 및 모델로 시작하는 하나의 통신과정을 만들 필요가 없다. 그래서 다시 post작업을 약간 수정해주도록 한다.
#views.py(backend)
class SignUpView(APIView):
def post(self, request):
# 사용자 입력값 가져오기
userId = request.data.get('userId')
userPassword = request.data.get('userPassword')
email = request.data.get('email', '') # 이메일이 없는 경우 빈 문자열로 처리
# 입력값 유효성 검사
if not userId or not userPassword:
return Response({'error': 'userId와 userPassword는 필수 입력값입니다.'}, status=status.HTTP_400_BAD_REQUEST)
# 새로운 사용자 생성 및 저장
try:
user = User.objects.create_user(username=userId, email=email, password=userPassword)
return Response({'message': '회원가입 성공'}, status=status.HTTP_201_CREATED)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
// 회원가입 //
SignUpSubmit() {
console.log("회원가입시도중");
const userSignUpData = {
userId: this.userId,
userPassword: this.userPassword,
email: "",
};
axios
.post("http://localhost:8000/api/signup/", userSignUpData)
.then((response) => {
console.log("회원가입 성공:", response.data);
this.isSignUpSuccessful = true;
// 성공한 경우 처리
})
.catch((error) => {
console.error("회원가입 실패:", error.response.data);
this.signUpError = true;
// 실패한 경우 처리
});
첫 마이그레이션때 여러 테이블이 생성되었었다. 당시엔 그것들이 뭐지 싶었지만, 적어도 우리는 이제 django.contrib.auth를 활용하게 되므로 auth_user를 사용하게 된다. 회원가입 코드에서
User.objects.create_user(username=userId, email=email, password=userPassword)
이 부분이 auth_user 테이블에 회원정보를 저장하고 이곳에서 일치검사를 진행하게 될 것이다. 모델과 시리얼라이저같은 논리는 모두 장고안에서 해결하고 있으니 굳이 개발자가 직접 만들 필요는 없다.
회원가입을 하나 진행했더니 다음과 같은 테이블이 생성되었다. 직접만든 것보다 훨씬 더 복잡한 데이터베이스 테이블이 만들어졌지만, 이용하기는 더 쉽다. 만들어둔 것을 사용함에 있어 효율성을 느낄 수 있다.
바로 로그인 과정을 구현해보자.
# views.py
class LoginView(APIView):
def post(self, request):
# 사용자 입력값 가져오기
username = request.data.get('username')
password = request.data.get('password')
email = request.data.get('email', '')
# 사용자 인증
user = authenticate(username=username, email=email, password=password)
if user is not None:
# 사용자 인증 성공
login(request, user)
return Response({'message': '로그인 성공'})
else:
# 사용자 인증 실패
return Response({'error': '유효하지 않은 사용자 정보입니다.'}, status=status.HTTP_401_UNAUTHORIZED)
// 로그인 화면단 메서드 구성
async Login() {
try {
const response = await axios.post("http://localhost:8000/api/login/", {
username: this.userId,
password: this.userPassword,
email: "",
});
console.log(response.data.message);
this.$router.push({ name: "mainpage" }); // 다음 페이지의 이름으로 네비게이션
// 로그인 성공 시
} catch (error) {
console.error(error);
// 로그인 실패 시
}
},
mainpage 컴포넌트를 제작해주고 로그인 성공시 해당 페이지로 넘어가게끔 만들었으며, LoginView에 해당하는 엔드포인트를 따로 urls.py에 정의해준다. login과정은 axios로 id,pw,email을 전달받으면 장고에서 authenticate메서드를 통해 진행된다.
rest_framwork를 사용중이므로, 토큰 방식을 활용해보도록 한다. restframework에서 지원하는 jwt를 사용하기 위해 몇가지 세팅작업을 한다.
우선 pip install djangorestframework_simplejwt로 관련 패키지를 다운받는다.
#setting.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
...
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
로그인뷰도 약간 수정해준다.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
class LoginView(APIView):
def post(self, request):
# 사용자 입력값 가져오기
username = request.data.get('username')
password = request.data.get('password')
email = request.data.get('email', '')
# 사용자 인증
user = authenticate(username=username, email=email, password=password)
if user is not None:
# 사용자 인증 성공시 토큰 생성
refresh = RefreshToken.for_user(user)
return Response({'message': '로그인 성공', 'refresh': str(refresh), 'access': str(refresh.access_token)})
else:
# 사용자 인증 실패
return Response({'error': '유효하지 않은 사용자 정보입니다.'}, status=status.HTTP_401_UNAUTHORIZED)
그 후 로그인 메서드 Login에
const token = response.data.access;
localStorage.setItem('token', token);
두 줄을 추가하고
Vue.js의 라우터 index.js에서 몇 가지를 수정해준다.
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import MainView from "../views/MainView.vue";
const routes = [
{
path: "/",
name: "login",
component: HomeView,
},
{
path: "/mainpage",
name: "mainpage",
component: MainView,
meta: { requiresAuth: true },
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
// Navigation Guards 설정
router.beforeEach((to, from, next) => {
// to: 이동하려는 라우트의 정보
// from: 현재 위치한 라우트의 정보
// next: 이 메소드를 호출하여 내비게이션을 계속할지 여부를 결정
const isAuthenticated = localStorage.getItem("access_token");
// 인증 여부 확인
if (
to.matched.some((record) => record.meta.requiresAuth) &&
!isAuthenticated
) {
next({ path: "/", query: { redirect: to.fullPath } });
} else {
next();
}
});
// 이동하려는 라우터에 requiresAuth가 존재하며 isAuthenticated가 false라면 "기본주소/"로 리디렉션한다. 만약 로그인을 다시해서 성공했다면 to.fullPath에 의해 원래 들어가려했던 페이지로 이동이 성공한다. 인증이 처음부터 성공한다면 그냥 next()를 통해 내비게이션을 지속한다. beforeEach는 페이지 이동전에 항상 실행되며, meta: { requiresAuth : true } 일 때만 진행한다.
export default router;
이러면 새로고침해도 로그인 유지 성공