JWT토큰을 발행하고 할당, 그리고 Validation까지 했으니, 이제 이 동작들을 Frontend로 가져가서 작업 해 보도록 하자
front-back의 소스는 여기 에 올려놓았다
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의 로직을 구상하는 것이 중요하다
//
// 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;
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의 템플릿을 가져와서 작업 한 것이기 때문에 크게 바꾼 것은 없지만, 작업 한 것은 버튼과 데이터를 담는 공간을 설정 한 것이다.
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>
makelogout() {
localStorage.removeItem('token');
location.reload();
},
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>
기존에는 Django, flask, spring 각각의 프레임워크 내에서 front,back을 모두 작업 했었는데 이거를 나눠서 하려니까 뭔가 중간에 과정이 하나 더 추가 되니까 플로우가 생각 할 것이 많은 것 같다