PWA

사용자 인증 - 개요, Router

그랜파 개발자 2024. 8. 1. 15:13

40. 계정 만들기, 그리고 로그인

계정 관리 모듈을 개발하기 위하여 ChatGPT에게 질문을 하고 확인을 하면서 개발에 필요한 기술들을 확보하였습니다. 이제 우리가 배운 기술들을 사용하여 직접 계정 관리 모듈을 만들어 보도록 하겠습니다.

기능 분석

1. 계정 등록

계정 등록은 구글에 합니다. 그렇지만 우리 웹앱의 계정 관리를 위하여 개발하는 웹앱에도 계정 등록을 해야 합니다. 우리 웹앱에 계정을 등록하는 방법은 현재 두 가지를 제공합니다.

1. 구글 계정이 없는 회원

구글 계정이 없는 회원은 이메일과 비밀번호를 입력하여 계정 등록을 하면 웹앱은 구글 계정에 회원을 등록하고, 회원의 등록 정보를 돌려받아 우리 웹앱에도 계정 등록을 합니다. 이때 구글에서 돌려받는 계정 정보는 이메일과 구글 계정의 uid이고 우리 웹앱에는 userid, name가 더 필요하므로 추가정보를 입력하고 웹앱에 계정 등록을 합니다.

계정 등록 시퀀스를 정리하면 다음과 같습니다.

  1. 계정 등록 창에 접속합니다. 계정 등록 창에는 이메일, 비밀번호, 비밀번호 확인, 그리고 계정 등록 버튼, 구글 계정 연동 버튼이 있습니다.
  2. 구글 계정이 없는 회원의 경우 이메일, 비밀번호,비밀번호 확인을 입력한 후 계정 등록을 누릅니다.
  3. 웹앱은 이메일과 비밀번호로 구글에 계정 등록을 합니다.
  4. 구글에 계정 등록을 성공하면 구글은 계정의 user 객체를 돌려줍니다.
  5. 웹앱은 구글이 돌려준 user 객체에서 email, uid를 가져와 웹앱의 계정 등록창을 나타냅니다.
  6. 회원은 웹앱의 계정 등록창에서 userid, name를 입력합니다. 그러면 userid, email, name, uid의 정보를 가지고 웹앱은 firestore에 계정을 등록합니다. 이때 uid는 다른 로그인 방법을 연동하게 될때 돌려 받는 uid 들이 함께 저장되는 배열입니다.

2. 구글에 계정이 있는 회원

  1. 계정 등록창에 접속합니다.
  2. 계정 등록창에서 구글 계정 연동 버튼을 누릅니다.
  3. 웹앱은 구글 로그인을 진행하여 로그인을 성공하면 user객체를 돌려 받습니다.
  4. 웹앱은 구글에서 돌려받은 user객체에서 이메일과 uid를 가져와 웹앱의 계정 등록창을 나타냅니다.
  5. 회원은 웹앱의 계정 등록창에서 userid, name를 입력합니다. 그러면 userid, email, name, uid의 정보를 가지고 웹앱은 firestore에 계정을 등록합니다. 이때 uid는 다른 로그인 방법을 연동하게 될때 돌려 받는 uid 들이 함께 저장되는 배열입니다.

2. 계정 정보

계정을 등록한 회원은 회원 정보 페이지에서 계정 등록 정보를 볼 수 있습니다.

3. 로그인

로그인은 이메일, 비밀번호를 입력하여 로그인하는 방법과 구글 로그인으로 로그인하는 방법을 제공합니다.

1. 이메일, 비밀번호 로그인

  1. 로그인 창에서 이메일, 비밀번호를 입력한 후 로그인 버튼을 누릅니다.
  2. 웹앱은 구글에 로그인을 하여 user 객체를 받아옵니다. 구글 로그인에 실패하면 더 이상 로그인을 진행하지 않습니다.
  3. 구글에서 받아온 user객체에서 uid로 웹앱의 계정정보를 가져옵니다. 웹앱의 계정에 uid가 없으면 로그인은 진행하지 않습니다.
  4. 웹앱에서 userid, email, name의 계정 정보를 store에 저장합니다. 그러면 웹앱은 어디서나 회원의 로그인 여부를 확인할 수 있습니다.

2. 구글 로그인

  1. 구글 로그인을 진행합니다. 구글 로그인이 되지 않으면 로그인을 더 이상 진행하지 않습니다.
  2. 구글로부터 user 객체를 돌려받아 여기서 uid로 웹앱의 계정 정보를 가져옵니다. 웹앱에 uid가 등록되어 있지 않으면 로그인을 진행하지 않습니다.
  3. 웹앱에서 userid, email, name의 계정 정보를 store에 저장합니다. 그러면 웹앱은 어디서나 회원의 로그인 여부를 확인할 수 있습니다.

4. 자동 로그인

로그인한 사용자는 다음에 접속을 하면 자동으로 로그인을 합니다.

계정 등록 웹앱 서비스 시나리오

위의 기능을 가진 웹앱이 어떻게 동작할지 그 시나리오를 생각해 봅니다.

1. 홈

누구나 우리의 웹앱에 방문을 하면 홈에 접속하게 됩니다. 현재 홈에는 그냥 홈이란 표시만 있을 것입니다. 홈에는 로그인 버튼과 계정 등록 버튼이 있습니다.

2. 로그인

로그인 버튼을 누르면 로그인 창이 나타나고, 로그인 방법은 이메일, 비밀번호 입력 후 로그인 버튼을 눌러 로그인 하거나 구글 로그인 버튼을 눌러 로그인 할 수 있습니다. 로그인 후에는 화면 우측 상단에 회원의 아이디가 나타나고, 회원의 아이디를 누르면 회원 정보를 볼 수 있습니다.

3. 계정 등록

계정 등록 버튼을 누르면 계정 등록 창이 나타나고, 계정 등록 창에는 이메일, 비밀번호, 비밀번호 확인을 입력할 수 있는 창과 계정 등록 버튼이 있고, 화면 아래에는 구글 계정 연동 버튼이 있습니다.

 

이메일과 비밀번호를 입력한 후 계정 등록 버튼을 누르면 웹앱의 계정 등록 창으로 변경이 되면서 구글이 돌려준 user 객체에서 이메일과 uid를 가져와 계정 정보 입력화면에 표시를 합니다. 회원은 화면에 나타난 userid와 name을 추가 입력한 후 계정 등록 버튼을 눌러 계정 등록을 완료합니다.

 

구글 계정 연동을 클릭하면 구글에 로그인을 하여 구글 계정을 받아 구글 계정의 이메일, uid를 웹앱 계정 등록 화면에 나타내고 사용자로부터 userid와 name을 추가 입력한 후 계정 등록 버튼을 눌러 웹앱에 계정 등록을 합니다. 어떤 방법으로 웹앱에 계정을 등록하든 웹앱의 계정 정보에서 userid는 유일해야 하고, uid는 배열입니다.

4. 웹앱의 화면

시나리오를 생각해보면 웹앱의 화면은 다음과 같이 구성됩니다.

  1. 로그인
  2. 계정등록 - 웹앱 계정 등록
  3. 계정 정보

ChatGPT 예제 분석

1. 예제의 서비스 시나리오

이제까지 우리가 ChatGPT에게 물어서 만든 예제를 분석하여 봅시다.

ChatGPT가 보여준 예제는 처음 로그인 화면을 보여줍니다. 로그인 화면은 email과 password를 입력한 후 로그인 버튼을 누르면 로그인이 진행됩니다. 예제는 로그인과 계정 등록 정보가 이메일,패스워드가 동일하여 로그인 화면과 계정 등록 화면을 같이 사용합니다.

로그인을 하면 'LOGOUT' 버튼과 등록된 회원의 리스트가 보이고, 각 회원은 삭제 아이콘과 수정할 수 있는 아이콘이 있습니다. 삭제 아이콘을 누르면 계정 정보는 삭제가 되고 수정 아이콘을 누르면 계정 정보를 수정할 수 있는 창이 나타납니다. 수정 창에서는 계정 정보를 입력한 후 'UPDATE USER' 버튼을 눌러 계정 정보를 수정할 수 있습니다. 또한 화면 아래에는 계정 정보를 등록할 수 있는 'ADD USER' 버튼도 있습니다

2. 예제의 코드 분석

계정과 관련된 각종 기능은 src/store/index.js에 있습니다. Vuex.Store로 각 컴포턴트에서의 상태를 여기에서 관리 할 수 있습니다.

Vue 개발에서 상태를 관리해 주는 기능을 제공해 주는 것이 Vuex이고 Vuex.Store는 어플리케이션의 모든 컴포넌트들에 대한 중앙 집중식 저장소의 역할 및 관리를 담당한다.

store 모듈 속에 actions 들입니다.

  • fetchUsers : firestore에 저장된 모든 계정 정보를 가져옵니다.
  • addUser : firestore에 계정 정보를 추가합니다.
  • deleteUser : 등록된 계정 정보를 삭제합니다.
  • updateUser : 등록된 계정 정보를 수정합니다.
  • login : auth.signInWithEmailAndPassword(email, password)
  • register : auth.createUserWithEmailAndPassword(email, password);
  • googleLogin : auth.signInWithPopup(googleProvider);
  • logout : auth.signOut();

예제는 등록된 계정의 리스트를 보여주고, 각 계정에 대해 수정과 삭제를 할 수 있으며 새로운 계정을 등록할 수 있는 기능도 있습니다. 그리고 이메일/비밀번호 입력을 통해 로그인을 할 수 있으며, 구글 로그인을 통해 로그인도 할 수 있습니다. 우리가 원하는 모든 기능이 에제 속에 포함되어 있습니다. 그런데 이것을 그대로 쓰기에는 우리의 구상과 많이 다릅니다. 그러나 필요한 기능들은 다 구현되어 있으므로 계정 등록 시나리오를 우리가 구상한 것과 같도록 구성하여 봅시다.

컴포넌트

컴포넌트 기초

https://ko.vuejs.org/guide/essentials/component-basics 참조

 

빌드 방식을 사용할 때 일반적으로 싱글 파일 컴포넌트(줄여서 SFC)라고 하는 .vue 확장자를 사용하는 전용 파일에 각 Vue 컴포넌트를 정의합니다:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">당신은 {{ count }} 번 클릭했습니다.</button>
</template>

컴포넌트 사용하기

자식 컴포넌트를 사용하려면 부모 컴포넌트에서 가져와야(import) 합니다. 파일 안에 ButtonCounter.vue라는 카운터 컴포넌트를 배치했다고 가정하면, 해당 컴포넌트 파일의 기본 내보내기가 노출됩니다:

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>아래에 자식 컴포넌트가 있습니다.</h1>
  <ButtonCounter />
</template>

 

<script setup>을 사용하면 가져온 컴포넌트를 템플릿에서 자동으로 사용할 수 있습니다.

컴포넌트는 원하는 만큼 재사용할 수 있습니다:

<h1>여기에 많은 자식 컴포넌트가 있습니다!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

 

SFC에서는 네이티브 HTML 엘리먼트와 구별하기 위해 자식 컴포넌트에 PascalCase 태그 이름을 사용하는 것이 좋습니다. 기본 HTML 태그 이름은 대소문자를 구분하지 않지만, Vue의 SFC는 컴파일된 포맷으로 대소문자를 구분하여 태그 이름을 사용할 수 있습니다. 또한 />를 사용하여 태그를 닫을 수 있습니다.

템플릿을 DOM에서 직접 작성하는 경우(예: 기본 <template> 엘리먼트의 컨텐츠로), 템플릿은 브라우저의 기본 HTML 구문 분석 동작을 따릅니다. 이러한 경우 컴포넌트는 kebab-case 및 명시적 닫는 태그를 사용해야 합니다:

<!-- 이 템플릿이 DOM에 작성된 경우 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

라우터

ChatGPT에게 물었습니다.

vue의 router에 대해 설명해줘

Vue Router는 Vue.js 애플리케이션에서 라우팅을 관리하는 공식 라이브러리로, SPA(Single Page Application)에서 중요한 역할을 합니다. Vue Router를 사용하면 URL 경로에 따라 서로 다른 컴포넌트를 렌더링하고, 애플리케이션의 상태를 유지하면서 다양한 뷰를 동적으로 전환할 수 있습니다.

1. Vue Router의 주요 기능

  1. 동적 라우팅: URL 경로와 컴포넌트를 매핑하여 특정 경로에 대해 어떤 컴포넌트를 렌더링할지 정의할 수 있습니다.
  2. 네스티드 라우트: 라우트를 중첩하여 복잡한 UI 구조를 쉽게 관리할 수 있습니다.
  3. 프로그램적 네비게이션: JavaScript 코드를 통해 경로를 변경하거나, 네비게이션을 제어할 수 있습니다.
  4. 라우트 매개변수: 경로에 변수를 포함시켜 동적인 데이터를 전달할 수 있습니다.
  5. 이동 가드: 라우트 전환 시 특정 조건을 검사하거나, 비동기 작업을 수행할 수 있습니다.

2. Vue Router 설치 및 설정

1. Vue Router 설치

npm install vue-router

2. 라우터 설정 파일 작성

src/router/index.js 파일을 생성하고, 라우트를 설정합니다.

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '@/components/Home.vue';
import About from '@/components/About.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
];

const router = new VueRouter({
  mode: 'history', // 'hash' 모드 대신 'history' 모드를 사용할 수도 있습니다.
  routes
});

export default router;

3. 루트 인스턴스에서 라우터 사용

src/main.js 파일에서 라우터를 Vue 인스턴스에 추가합니다.

import Vue from 'vue';
import App from './App.vue';
import router from './router';

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

4. <router-view> 사용

App.vue 파일에서 <router-view>를 사용하여 현재 활성화된 라우트에 해당하는 컴포넌트를 렌더링합니다.

<!-- src/App.vue -->
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
    </nav>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
};
</script>

3. 네스티드 라우트 (중첩 라우트)

네스티드 라우트는 부모 라우트 내에 자식 라우트를 정의하여, 더 복잡한 UI를 관리할 수 있게 합니다.

// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '@/components/Home.vue';
import About from '@/components/About.vue';
import Profile from '@/components/Profile.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About,
    children: [
      {
        path: 'profile',
        name: 'Profile',
        component: Profile
      }
    ]
  }
];

const router = new VueRouter({
  mode: 'history',
  routes
});

export default router;

부모 컴포넌트에서 <router-view>를 사용하여 자식 라우트를 렌더링할 위치를 지정합니다.

<!-- src/components/About.vue -->
<template>
  <div>
    <h1>About Page</h1>
    <router-link to="/about/profile">Go to Profile</router-link>
    <router-view></router-view> <!-- 자식 라우트를 렌더링할 위치 -->
  </div>
</template>

<script>
export default {
  name: 'About'
};
</script>

4. 프로그램적 네비게이션

JavaScript를 통해 라우트를 프로그래밍 방식으로 변경할 수 있습니다.

<template>
  <div>
    <button @click="goHome">Go to Home</button>
  </div>
</template>

<script>
export default {
  methods: {
    goHome() {
      this.$router.push('/');
    }
  }
};
</script>

5. 라우트 매개변수

라우트 매개변수를 사용하여 동적인 데이터를 라우트에 전달할 수 있습니다.

// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import User from '@/components/User.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/user/:id',
    name: 'User',
    component: User
  }
];

const router = new VueRouter({
  mode: 'history',
  routes
});

export default router;

라우트 매개변수를 사용하는 컴포넌트에서 this.$route.params를 통해 매개변수에 접근할 수 있습니다.

<!-- src/components/User.vue -->
<template>
  <div>
    <h1>User ID: {{ userId }}</h1>
  </div>
</template>

<script>
export default {
  computed: {
    userId() {
      return this.$route.params.id;
    }
  }
};
</script>

요약

Vue Router는 Vue.js 애플리케이션에서 라우팅을 관리하는 공식 라이브러리입니다. <router-view> 는 현재 활성화된 라우트에 따라 컴포넌트를 렌더링하는 자리 표시자 역할을 합니다. 네스티드 라우트를 통해 복잡한 UI를 중첩된 라우팅 구조로 관리할 수 있습니다. 프로그램적 네비게이션을 통해 JavaScript 코드로 라우트를 변경할 수 있습니다. 라우트 매개변수를 사용하여 동적인 데이터를 라우트에 전달할 수 있습니다. 이러한 기능들을 통해 Vue.js 애플리케이션에서 SPA를 쉽게 구현하고 관리할 수 있습니다.