이전 글 첨부
[Master_branch/develop_branch] - Vue.js & Flask - 개발환경구축하기
[Master_branch/develop_branch] - Vue.js & Flask - 컴포넌트와 api 연동(1) - backend
[Master_branch/develop_branch] - Vue.js & Flask - 컴포넌트와 api 연동(2) - frontend
[Master_branch/develop_branch] - Vue.js & Flask - 데이터 주고받기 (axios)
[Master_branch/develop_branch] - Vue.js & Flask - 데이터 주고받기 axios(2) button binding
[Master_branch/develop_branch] - Vue.js & Flask 로그인/로그아웃 (1) - Docker DB 세팅하기
[Master_branch/develop_branch] - Vue.js & Flask 로그인/로그아웃 (2) - Flask Auth API 만들기
[Master_branch/develop_branch] - Vue.js & Flask 로그인/로그아웃 (3) - Flask Auth Token 적용하기
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의 로직을 구상하는 것이 중요하다
- 로그인
로그인 후 네비게이션 바 위에 내 이름이 뜨도록 - 로그아웃
로그아웃 후 네비게이션 바 위에 이름이 없어지도록 - 게시글 리스트
미리 저장되어있는 게시글 리스트를 불러 올 때, 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 변경
-
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" 으로 버튼과 함수를 이어 주었다.
-
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을 모두 작업 했었는데 이거를 나눠서 하려니까 뭔가 중간에 과정이 하나 더 추가 되니까 플로우가 생각 할 것이 많은 것 같다
'Old Branch' 카테고리의 다른 글
[Docker 위의 웹서버] 프로젝트 세팅 (2) - ELK 세팅 (0) | 2020.08.25 |
---|---|
[Docker 위의 웹서버] 프로젝트 세팅 (1) - ELK-Flask 패키지 준비 (0) | 2020.08.24 |
Vue.js & Flask 로그인/로그아웃 (3) - Flask Auth Token 적용하기 (0) | 2020.08.22 |
Vue.js & Flask 로그인/로그아웃 (2) - Flask Auth API 만들기 (0) | 2020.08.21 |
Vue.js & Flask 로그인/로그아웃 (1) - Docker DB 세팅하기 (0) | 2020.08.20 |