Vue.js & Flask 로그인/로그아웃 (4) - Vue.js Component 연동하기

울이·2020년 8월 3일
0
post-thumbnail

JWT토큰을 발행하고 할당, 그리고 Validation까지 했으니, 이제 이 동작들을 Frontend로 가져가서 작업 해 보도록 하자

front-back의 소스는 여기 올려놓았다

JWT Auth In Frontend

structure

  • frontend 구조라 스트럭쳐가 꽤 길다

    .
    ├── CHANGELOG.md
    ├── ISSUES_TEMPLATE.md
    ├── LICENSE.md
    ├── README.md
    ├── babel.config.js
    ├── front.dev.Dockerfile
    ├── package.json
    ├── public
    │    ....
    │   ├── index.html
    │   ├── manifest.json
    │   └── robots.txt
    ├── src
    │   ├── App.vue
    │   ├── assets
    │   │ ...
    │   ├── components
    │   │   ├── Badge.vue
    ... 
    │   │   ├── Tweet
    │   │   │   └── Tweet.vue
    │   │   └── stringUtils.js
    │   ├── directives
    │   │   └── click-ouside.js
    │   ├── layout
    │   │   ├── AuthLayout.vue
    │   │   ├── Content.vue
    │   │   ├── ContentFooter.vue
    │   │   ├── DashboardLayout.vue
    │   │   └── DashboardNavbar.vue
    │   ├── main.js
    │   ├── plugins
    │   │   ├── argon-dashboard.js
    │   │   ├── globalComponents.js
    │   │   └── globalDirectives.js
    │   ├── registerServiceWorker.js
    │   ├── router.js
    │   ├── utils
    │   │   └── index.js
    │   └── views
    │       ├── Dashboard
    │       │   ├── PageVisitsTable.vue
    │       │   └── SocialTrafficTable.vue
    │       ├── Dashboard.vue
    │       ├── Icons.vue
    │       ├── Login.vue
    │       ├── Maps.vue
    │       ├── MyLittleDiv
    │       │   └── MyLittleDiv.vue
    │       ├── Register.vue
    │       ├── Tables
    │       │   └── ProjectsTable.vue
    │       ├── Tables.vue
    │       └── UserProfile.vue
    ├── vue.config.js
    └── yarn.lock

적용 할 로직

backend의 코드에서 필요한 정보를 넘겨주기 때문에 ,front의 로직을 구상하는 것이 중요하다

  1. 로그인
    로그인 후 네비게이션 바 위에 내 이름이 뜨도록
  2. 로그아웃
    로그아웃 후 네비게이션 바 위에 이름이 없어지도록
  3. 게시글 리스트
    미리 저장되어있는 게시글 리스트를 불러 올 때, token값을 통해 인증 할 수 있도록

공통 Token Valid 함수

//
// frontend/src/utils/index.js

import Vue from 'vue'

export const EventBus = new Vue()

export function isValidJwt() {

    let jwt = localStorage.token

    if (!jwt || jwt.split('.').length < 3) {
        return false
    }
    const data = JSON.parse(atob(jwt.split('.')[1]))
    const exp = new Date(data.exp * 1000) // JS deals with dates in milliseconds since epoch
    const now = new Date()
    return now < exp
}

export default isValidJwt;

로그인

  • ID,PW 입력 → 로그인 요청 → backend → 토큰발행 → 토큰을 front로
  • 응답 받은 토큰을 가지고 frontend의 여러 곳에서 적용 할 수 있도록 공통모듈함수 생성

Login.vue, DashboardNavbar.vue 변경

  1. frontend/src/views/Login.vue

    //frontend/src/views/Login.vue - script 부분
    <script>
        import axios from 'axios';
    
        export default {
            name: 'login',
            data() {
                return {
                    userInfo: {
                        username: '',
                        userpwd: ''
                    }
                }
            },
            methods: {
                makeLogin() {
                    let path = "http://" + window.location.hostname + ":5000/api2/auth/login";
                    axios.post(path, {
                        username: this.userInfo.username,
                        userpwd: this.userInfo.userpwd
                    }, {withCredential: true}).then((res) => {
                        localStorage.token = res.data.token
                        this.$router.push("/")
                        // if(this.userInfo.username == )
                    }).catch((error) => {
                        console.log(error);
                    });
                },
            },
        }
    </script>

    javascript에서 userInfo에 user 정보를 담고, methods에서 로그인을 도와줄 makeLogin()함수를 만든다.

    로그인이 완료 된 후에, backend에서 받은 토큰을 크롬 로컬 스토리지에 저장하고 Dashboard페이지로 돌아갈 수 있도록

    이 모든 통신에 axios를 사용 할 것이기 때문에 설치 되어 있지 않다면 yarn, npm을 이용해서 설치 해 주어야 한다.

    //frontend/src/views/Login.vue - 위의 스크립트를 적용시킬 vue파일
    <template>
        <div class="row justify-content-center">
            <div class="col-lg-5 col-md-7">
                <div class="card bg-secondary shadow border-0">
                    <div class="card-header bg-transparent pb-5">
                        <div class="text-muted text-center mt-2 mb-3"><small>Sign in with</small></div>
                        <div class="btn-wrapper text-center">
                            <a href="#" class="btn btn-neutral btn-icon">
                                <span class="btn-inner--icon"><img src="img/icons/common/github.svg"></span>
                                <span class="btn-inner--text">Github</span>
                            </a>
                            <a href="#" class="btn btn-neutral btn-icon">
                                <span class="btn-inner--icon"><img  src="img/icons/common/google.svg"></span>
                                <span class="btn-inner--text">Google</span>
                            </a>
                        </div>
                    </div>
                    <div class="card-body px-lg-5 py-lg-5">
                        <div class="text-center text-muted mb-4">
                            <small>Or sign in with credentials</small>
                        </div>
                        <form role="form">
                            <base-input class="input-group-alternative mb-3"
                                        placeholder="text"
                                        addon-left-icon="ni ni-email-83"
                                        v-model="userInfo.username">
                            </base-input>
    
                            <base-input class="input-group-alternative"
                                        placeholder="Password"
                                        type="password"
                                        addon-left-icon="ni ni-lock-circle-open"
                                        v-model="userInfo.userpwd">
                            </base-input>
    
                            <base-checkbox class="custom-control-alternative">
                                <span class="text-muted">Remember me</span>
                            </base-checkbox>
                            <div class="text-center">
                                <base-button type="primary" class="my-4" v-on:click="makeLogin">Sign in</base-button>
                            </div>
                        </form>
                    </div>
                </div>
                <div class="row mt-3">
                    <div class="col-6">
                        <a href="#" class="text-light"><small>Forgot password?</small></a>
                    </div>
                    <div class="col-6 text-right">
                        <router-link to="/register" class="text-light"><small>Create new account</small></router-link>
                    </div>
                </div>
            </div>
        </div>
    </template>

    모두 기존 argon의 템플릿을 가져와서 작업 한 것이기 때문에 크게 바꾼 것은 없지만, 작업 한 것은 버튼과 데이터를 담는 공간을 설정 한 것이다.

    • Sign in 버튼에 v-on:click="makeLogin" 으로 버튼과 함수를 이어 주었다.
  2. front/src/layout/DashboardNavbar.vue 변경

    //front/src/layout/DashboardNavbar.vue - script 부분
    <script>
        import isValidJwt from '../utils'
    
        export default {
            data() {
                return {
                    username: '',
                    activeNotifications: false,
                    showMenu: false,
                    searchQuery: ''
                };
            },
            methods: {
                makelogout() {
                    localStorage.removeItem('token');
                    location.reload();
                },
                isAuthenticated() {
                    if (isValidJwt()) {
                        let data = JSON.parse(atob(localStorage.token.split('.')[1]))
                        this.username = data.sub
                    }
                },
                toggleSidebar() {
                    this.$sidebar.displaySidebar(!this.$sidebar.showSidebar);
                },
                hideSidebar() {
                    this.$sidebar.displaySidebar(false);
                },
                toggleMenu() {
                    this.showMenu = !this.showMenu;
                }
            },
            created() {
                this.isAuthenticated()
            }
        };
    </script>

    이 스크립트에서 중요 한 것은, username을 새로 넣어주는 것이다. 간단한 axios 작업이기 때문에 큰 어려움은 없다.

    //front/src/layout/DashboardNavbar.vue - template부분
    <template>
        <base-nav class="navbar-top navbar-dark"
                  id="navbar-main"
                  :show-toggle-button="false"
                  expand>
            <form class="navbar-search navbar-search-dark form-inline mr-3 d-none d-md-flex ml-lg-auto">
                <div class="form-group mb-0">
                    <base-input placeholder="Search"
                                class="input-group-alternative"
                                alternative=""
                                addon-right-icon="fas fa-search">
                    </base-input>
                </div>
            </form>
            <ul class="navbar-nav align-items-center d-none d-md-flex">
                <li class="nav-item dropdown">
                    <base-dropdown class="nav-link pr-0">
                        <div class="media align-items-center" slot="title">
                    <span class="avatar avatar-sm rounded-circle">
                      <img alt="Image placeholder" src="img/theme/team-4-800x800.jpg">
                    </span>
                            <div class="media-body ml-2 d-none d-lg-block">
                                <span class="mb-0 text-sm  font-weight-bold">{{ username }}</span>
                            </div>
                        </div>
    
                        <template>
                            <div class=" dropdown-header noti-title">
                                <h6 class="text-overflow m-0">Welcome!</h6>
                            </div>
                            <router-link to="/profile" class="dropdown-item">
                                <i class="ni ni-single-02"></i>
                                <span>My profile</span>
                            </router-link>
                            <router-link to="/profile" class="dropdown-item">
                                <i class="ni ni-settings-gear-65"></i>
                                <span>Settings</span>
                            </router-link>
                            <router-link to="/profile" class="dropdown-item">
                                <i class="ni ni-calendar-grid-58"></i>
                                <span>Activity</span>
                            </router-link>
                            <router-link to="/profile" class="dropdown-item">
                                <i class="ni ni-support-16"></i>
                                <span>Support</span>
                            </router-link>
                            <div class="dropdown-divider"></div>
                            <a v-on:click="makelogout">
                                <router-link to="/" class="dropdown-item">
                                    <i class="ni ni-user-run"></i>
                                    <span>Logout</span>
                                </router-link>
                            </a>
                        </template>
                    </base-dropdown>
                </li>
            </ul>
        </base-nav>
    </template>

로그아웃

  • 로그아웃은, 로컬스토리지에 저장된 토큰을 삭제 해 주고 refresh해 주는 형태로 작업했다.
  • 더 좋은 방법이 10000000% 있는데 나는 아직은 잘 모르겠다ㅜㅜ 도와주면 좋겠다 누가....
  • 위의 로그인 작업 하는 DashboardNavbar.vue 에서, 로그아웃 메뉴가 있어서 같이 작업했다.
makelogout() {
      localStorage.removeItem('token');
      location.reload();
  },
  • 다른 코드 없이 단지 Storage의 token 값을 비워주었다

Tweet(게시글 리스트)

backend에서 작성했었던 Tweet 리스트 조회에 @token_required 을 적용한 url에서는 token값이 인증 된 것이 아니라면 401 에러를 뱉어내고 조회하지 못한다.

Tweet는 컴포넌트를 하나 만들고, Dashboard.vue에 추가 해 주는 형태로 작성했다.

<template>
    <div class="Tweet">
        <div class="row" v-for="tweet in tweet_list" :key="tweet.id">
            <div class="col-xl-12 col-lg-12">
                <stats-card title="Total traffic"
                            type="gradient-red"
                            icon="ni ni-active-40"
                            class="mb-4 mb-xl-0"
                >
                    <span style="font-size: 30px">{{ tweet.words }}</span>
                    <template slot="footer">
                        <span class="text-success mr-2" style="font-size: 25px"><i class="fa fa-user"
                                                                                   aria-hidden="true"></i>  {{ tweet.creator }}</span>
                        <span class="text-nowrap">{{ tweet.created_at }}</span>
                    </template>
                </stats-card>
            </div>
        </div>
    </div>
</template>

<script>
    import axios from 'axios';

    export default {
        name: 'all-tweet',
        data() {
            return {
                tweet_list: [],
            }
        },
        methods: {
            getTweet() {
                let path = "http://" + window.location.hostname + ":5000/api2/board/tweet";
                let token = localStorage.token
                axios.defaults.headers.common['Authorization'] = `Bearer: ${token}`
                axios.get(path).then((res) => {
                    this.tweet_list = res.data;
                }).catch((error) => {
                    console.error(error);
                });
            }
        },
        created() {
            this.getTweet();
        }
    };
</script>
  • vue-for 문을 사용해서 여러개의 글을 출력 해 주었다
  • script의 axios에서는 다른 axios와 다르게 defaults.headers를 추가 해 주었다
  • 토큰 인증을 사용 하는 요청/응답 과정이기 때문에 적용 해 주었다.

어렵다뭔가...

기존에는 Django, flask, spring 각각의 프레임워크 내에서 front,back을 모두 작업 했었는데 이거를 나눠서 하려니까 뭔가 중간에 과정이 하나 더 추가 되니까 플로우가 생각 할 것이 많은 것 같다

profile
개발을 개발개발

0개의 댓글