- [개발자의품격][부트캠프][1기][24차시] Vue.js #17 | 로그인 기능(vuex-persistedstate, vue-cookies 등 사용)2022년 03월 20일 20시 51분 42초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
로그인 기능
src/views/LoginView.vue 컴포넌트를 생성한 후 src/router/index.js에 경로 설정을 한다. vuex 관련 코드는 일단 주석 처리한다.
// src/router/index.js ... import LoginView from '../views/LoginView.vue' // import store from '../store' ... const routes = [ ... { path: '/login', name: 'LoginView', component: LoginView }, ... // router.beforeEach((to, from, next) => { // if (to.path === '/') { // next() // } else if (to.path === '/vuex/todo') { // next() // } else { // if (store.getters['user/isLogin']) { // next() // } else { // next('/vuex/todo') // } // } // })
Bootstrap > Examples > Sign-in(https://getbootstrap.com/docs/5.1/examples/sign-in/)에서 소스보기(Ctrl+U)하여 HTML 소스는 <template> 안에 CSS 소스는 <style scope> 안에 각각 복붙 한다. <form> 태그를 <div>로 변경한다. 이번 실습에서는 <form> 태그를 사용하지 않는다. 완료되면 기존 화면은 [그림 1]과 같은 화면으로 변경된다.
<!-- src/views/LoginView.vue --> <template> <main class="form-signin"> <div> <img class="mb-4" src="@/assets/logo.png" alt="" width="72" height="72" /> <h1 class="h3 mb-3 fw-normal">Please sign in</h1> <div class="form-floating"> <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" /> <label for="floatingInput">Email address</label> </div> <div class="form-floating"> <input type="password" class="form-control" id="floatingPassword" placeholder="Password" /> <label for="floatingPassword">Password</label> </div> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me" /> Remember me </label> </div> <button class="w-100 btn btn-lg btn-primary" type="submit"> Sign in </button> <p class="mt-5 mb-3 text-muted">© 2017–2021</p> </div> </main> </template> <script> export default { components: {}, data() { return { sampleData: '' } }, setup() {}, created() {}, mounted() {}, unmounted() {}, methods: {} } </script> <style scoped> html, body { height: 100%; } body { display: flex; align-items: center; padding-top: 40px; padding-bottom: 40px; background-color: #f5f5f5; } .form-signin { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-signin .checkbox { font-weight: 400; } .form-signin .form-floating:focus-within { z-index: 2; } .form-signin input[type='email'] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type='password'] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; } </style>
<script> 태그 내 data()에 email, pw 데이터를 넣는다. <template> 태그 내 <input type="email"...>, <input type="password"...> 태그 내에 email, pw 데이터를 각각 v-model로 바인딩한다.
<template> 태그 내 Sign-in 버튼의 type="submit"을 @click="login"으로 변경하고, <script> 태그 내 methods 내에 login() 함수를 선언한다.
<!-- src/views/LoginView.vue --> <template> <main class="form-signin"> <div> ... <div class="form-floating"> <input type="email" ... v-model="email" /> ... </div> <div class="form-floating"> <input type="password" ... v-model="pw" /> ... <button class="w-100 btn btn-lg btn-primary" @click="login"> Sign in </button> ... </template> <script> export default { ... data() { return { email: '', pw: '' } }, ... methods: { login() { this.$store.commit('user/setUser', { name: 'Sewol', email: 'sewol@gmail.com' }) } } ...
route path에 navigation이 보이지 않게 처리
routes의 index.js에서 루트 경로를 LoginView.vue 컴포넌트와 연결하는 route를 추가한다.
// src/router/index.js ... const routes = [ { path: '/', name: 'login', component: LoginView }, { path: '/home', name: 'home', component: HomeView }, { path: '/login', name: 'login2', component: LoginView }, ...
components 폴더 내에 layouts/HeaderLayout.vue를 생성하고 App.vue의 모든 소스코드를 HeaderLayout.vue에 복사한다. 이때 아래와 같이 일부 코드를 주석 처리(또는 삭제)한다.
<!-- src/components/layouts/HeaderLayout.vue --> <template> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/hello">Hello</router-link> </nav> <!-- <router-view /> --> </template> <style> /* #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } */ nav { padding: 30px; } nav a { font-weight: bold; color: #2c3e50; } nav a.router-link-exact-active { color: #42b983; } </style>
App.vue에 HeaderLayout.vue를 import 하고, 경로가 루트가 아닐 때 HeaderLayout을 보여주도록 한다.
<!-- src\App.vue --> <template> <!-- <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/hello">Hello</router-link> </nav> --> <HeaderLayout v-if="$route.path != '/'" /> <router-view /> </template> <script> import HeaderLayout from '@/components/layouts/HeaderLayout.vue' export default { components: { HeaderLayout } } </script> ...
LoginView.vue 컨포넌트의 login() 함수에서 $router를 이용해 로그인이 되면 /home으로 이동시킨다.
// src/views/LoginView.vue ... <script> export default { ... methods: { login() { this.$store.commit('user/setUser', { name: 'Sewol', email: 'sewol@gmail.com' }) this.$router.push({ path: '/home' }) // 추가 }
HeaderLayout.vue 컴포넌트에 Bootstrap를 적용해 NavBar를 꾸며준다. Bootstrap의 Examples에서 Carousel(https://getbootstrap.com/docs/5.1/examples/carousel/을 선택하고 소스보기에서 <header> 부분만 <template> 태그 내에 복붙 한다. 코드 내 주석 처리된 부분은 수정한 코드이다.
<!-- src/components/layouts/HeaderLayout.vue --> <template> <header> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container-fluid"> <a class="navbar-brand" href="#">Carousel</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation" > <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav me-auto mb-2 mb-md-0"> <li class="nav-item"> <!-- <a class="nav-link active" aria-current="page" href="#">Home</a> --> <a class="nav-link active" aria-current="page" @click="goToMenu('/home')" >Home</a > </li> <li class="nav-item"> <!-- <a class="nav-link" href="#">Link</a> --> <a class="nav-link" @click="goToMenu('/about')">About</a> </li> <!-- <li class="nav-item"> <a class="nav-link disabled">Disabled</a> </li> --> </ul> <form class="d-flex"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" /> <button class="btn btn-outline-success" type="submit"> Search </button> </form> </div> </div> </nav> </header> </template>
<script> 태그 내에 goTomenu() 함수를 선언한다.
// src/components/layouts/HeaderLayout.vue <script> export default { methods: { goToMenu(path) { this.$router.push({ path: path }) } } } </script>
<style> 태그 내에 header의 스타일을 지정한다. NavBar에 페이지가 가려지는 문제를 해결하기 위해서이다.
// src/components/layouts/HeaderLayout.vue <style> header { margin-bottom: 70px; } </style>
[그림 2]는 About 페이지를 선택했는데 페이지는 정상적으로 보여주나 NavBar는 Home을 선택한 것으로 표시하고 있다. 이 문제를 해결하기 위해서 :class 코드를 추가한다.
<!-- src/components/layouts/HeaderLayout.vue --> <template> ... <li class="nav-item"> <a class="nav-link" :class="{ active: $route.path == '/home' }" aria-current="page" @click="goToMenu('/home')" >Home</a> </li> <li class="nav-item"> <a class="nav-link" :class="{ active: $route.path == '/about' }" @click="goToMenu('/about')" >About</a> </li> ...
로그인한 경우 [그림 3]과 같이 userInfo가 표시되도록 하고, 로그아웃 버튼을 만든다.
<!-- src/components/layouts/HeaderLayout.vue --> <template> ... <div class="d-flex"> <span v-if="userInfo.name" class="text-white">{{ userInfo.name }}</span> <button class="btn btn-outline-success">로그아웃</button> </div> ...
사용자 정보를 가져오는 userInfo() 함수를 선언한다.
// src/components/layouts/HeaderLayout.vue export default { computed: { userInfo() { return this.$store.state.user.userInfo } },
methods에 logout() 함수를 선언하여 로그아웃 기능을 구현한다.
<!-- src/components/layouts/HeaderLayout.vue --> <template> ... <button class="btn btn-outline-success" @click="logout"> 로그아웃 </button> ... <script> ... methods: { ... logout() { this.$store.commit('user/setUser', {}) this.$router.push({ path: '/' }) } ...
vuex-persistedstate
새로고침 하면 사용자 정보가 사라진다. 사용자 정보가 사라지지 않도록 하고자 한다. 터미널 창에서 vuex-persistedstate를 설치한다.
npm install vuex-persistedstate
store폴더의 index.js에 vuex-persistedstate를 import 한다. persistedstate 객체의 paths의 값에 상태를 영속적으로 유지하고자 하는 사용자 정보를 넣으면 새로고침이 일어나더라도 사용자 정보가 영구히 유지된다. 이것이 가능한 이유는 [그림 4]와 같이 사용자 정보가 Local storage 안에 있기 때문이다.
// src/store/index.js ... import persistedstate from 'vuex-persistedstate' export default createStore({ ... plugins: [persistedstate({ paths: ['user.userInfo'] })] })
Local storage 내에 민감한 정보가 남아 있을 경우 문제가 될 수 있다. 따라서 [그림 5]와 같이 로그아웃할 때 사용자 정보가 사라지게 만들어야 한다. (이러한 처리를 해도 로그아웃 없이 창을 닫으면 사용자 정보가 남아있게 된다.)
// src/store/user.js export const user = { ... mutations: { ... logout(state) { state.userInfo = {} } ...
// src/components/layouts/HeaderLayout.vue <script> export default { ... methods: { ... logout() { this.$store.commit('user/logout', {}) ... }
vue-cookies
vue-cookies 모듈을 설치한다. vue-cookies는 설정된 시간에 자동 로그아웃되게 할 수 있다.
npm install vue-cookies
// src/store/user.js import VueCookies from 'vue-cookies' export const user = { ... getters: { isLogin(state) { // if (state.userInfo.name) { // return true // } else { // return false // } if (VueCookies.get('userInfo')) { return true } else { return false } } }, mutations: { setUser(state, userInfo) { ... VueCookies.set('userInfo', userInfo, '1MIN') }, logout(state) { ... VueCookies.remove('userInfo') } ...
// src/router/index.js ... import store from '../store' ... ... router.beforeEach((to, from, next) => { if (to.path === '/') { next() } else { if (store.getters['user/isLogin']) { next() } else { store.commit('/user/logout') next('/') } } }) ...
실무에서는 위와 같이 클라이언트에서 인증 섹션을 관리하지 않고 JSON 토큰 기반으로 서버에서 관리한다.
728x90반응형'영광의 시대! > 2022 개발자의 품격 부트캠프 1기' 카테고리의 다른 글
[개발자의품격][부트캠프][1기][27차시] MySQL #1 | 테이블 생성, ERD (0) 2022.03.30 [개발자의품격][부트캠프][1기][24차시] Vue.js #18 | Footer(Bootstrap, Font Awesome의 Font icon) (0) 2022.03.21 [개발자의품격][부트캠프][1기][23차시] Vue.js #16 | Vuex (0) 2022.03.19 [개발자의품격][부트캠프][1기][23차시] Vue.js #15 | Formatter - 금액 처리 (0) 2022.03.18 [개발자의품격][부트캠프][1기][22차시] Vue.js #14 | Formatter - 날짜 처리 (0) 2022.03.17 다음글이 없습니다.이전글이 없습니다.댓글