Bob's Blog

Web开发、测试框架、自动化平台、APP开发、机器学习等

返回上页首页

Django restframework加Vue打造前后端分离的网站(七)登录并保持状态



restful api是一种无状态的请求,每个请求与其他请求隔离,也不会记录交互的场景,不在意反馈请求的服务端是哪一个。restful架构要求服务器端不保有任何与特定HTTP请求相关的资源,所以应用状态必须由请求方在请求过程中提供。

既然后端采用了restful api的实现方式,那么用了token验证方式也是一种无状态的,两者观念相符。

token相比session来说有如下区别:

1)减轻服务器压力,不用在服务器端维持用户session;

2)session是浏览器特有的,如果后端服务也需要支持手机app,那么token则是好一点的选择。

3)支持跨域,token可以少考虑CSRF跨站请求伪造的问题。

4)token是无状态的(stateless),session则是维持状态的(presist)

5)当有多服务器时,token需要同步。不过session也是只维持在一个服务器上的。

6)app或者单页面应用(SPA)或者第三方登录推荐用token,多页面应用(MPA)推荐用session。

--------------------------------------------------

前面说了这么多些,是因为打算用token验证,但是前端获取token后,刷新页面就会丢失登录状态。该篇文章会记录下如何集成登录以及保持状态。

我们可以用vuex来管理全局状态,可随时读取和修改state,从后端获取token并记录,跳转新页面做新的后台请求时,就会带上该token,否则返回登录页面,如果token过期也会更新该状态。就能记录用户的登录状态并能判断是否需要再次登录。

先安装vuex

npm install --save vuex

在frontend/src下创建store/index.js,分别有state / mutations / actions / getters, 在actions这里面写了login和logout两个方法,每次调用时会更新到state里的状态的值里面去,并存储在local storage中,因为虽然状态会保存在vuex中,但如果用户关闭浏览器状态就会丢失了,再次打开时若token仍然有效,应仍然让用户处于登录状态。

// store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    status: '',
    token: localStorage.getItem('token') || '',
    user : localStorage.getItem('user') || ''
  },

  mutations: {
    auth_request(state){
      state.status = 'loading'
    },
    auth_success_token(state, token){
      state.status = 'success'
      state.token = token
      // state.user = user // 这里传递第二个参数却会是undefined,未找到原因,所以将token和user分成两个
    },
    auth_success_user(state, user){
      state.status = 'success'
      state.user = user
    },
    auth_error(state){
      state.status = 'error'
    },
    logout(state){
      state.status = ''
      state.token = ''
      state.user = ''
    },
  },

  actions: {
    login({commit}, user){
        return new Promise((resolve, reject) => {
          commit('auth_request')
          axios({url: 'http://127.0.0.1:8000/automation/api/api-token-auth/', data: user, method: 'POST' })
          .then(resp => {
            const token = resp.data.token
            const user = resp.data.username
            localStorage.setItem('token', token)
            localStorage.setItem('user', user)
            axios.defaults.headers.common['Authorization'] = 'Token ' + token  // 这里是在登录后将token加到默认的header中,便于后续调用需要登录权限的api
            commit('auth_success_token', token)
            commit('auth_success_user', user)
            resolve(resp)
          })
          .catch(err => {
            commit('auth_error')
            localStorage.removeItem('token')
            reject(err)
          })
        })
    },

    logout({commit}){
      return new Promise((resolve, reject) => {
        commit('logout')
        localStorage.removeItem('token')
        localStorage.removeItem('user')
        delete axios.defaults.headers.common['Authorization']
        resolve()
      })
    }
  },

  getters : {
    isLoggedIn: state => !!state.token,
    authStatus: state => state.status,
  }
})

在main.js中引用该文件,并设置如果token存在就会带上默认的header,便于调用需要登录权限的api。

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import axios from 'axios'

Vue.prototype.$http = axios;
const token = localStorage.getItem('token')
console.log(token)
if (token) {
  Vue.prototype.$http.defaults.headers.common['Authorization'] = 'Token ' + token
}

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

修改home.vue文件,添加login入口和一些状态展示,当然还是测试用。当logout时,就会销毁记录的状态和token。

<template>
  <div>
    <h3>{{ msg }}</h3>
    <button type="button" @click='getProjects' :style="{ margin: '10px', padding: '5px' }">get projects</button>
    <a v-if="this.$store.state.token" @click="logout" :style="{ margin: '10px', padding: '5px' }">Logout</a>
    <router-link v-else to="/login" :style="{ margin: '10px', padding: '5px' }">Login</router-link>
    <p>{{ auth_text }}</p>
    <p>{{ this.$store.state.token }}</p>
    <p>{{ this.$store.state.status }}</p>
    <p>{{ this.$store.state.user }}</p>
    <p>{{ this.$store.getters.isLoggedIn }}</p>
    <p>{{ this.$store.getters.authStatus }}</p>
    <table v-if="auth.length > 0" :style="{ border: '2px solid gray', borderRadius: '5px', padding: '10px' }" align='center'>
      <tr v-for='a in auth'>
        <td>{{ a.id }}</td>
        <td>{{ a.username }}</td>
        <td>{{ a.email }}</td>
        <td>{{ a.token }}</td>
      </tr>
    </table>
    <table :style="{ border: '2px solid gray', borderRadius: '5px', padding: '10px' }" align='center'>
      <tr v-for='p in projects'>
        <td>{{ p.id }}</td>
        <td>{{ p.name }}</td>
        <td>{{ p.create_time }}</td>
        <td>{{ p.update_time }}</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data () {
    return {
      msg: 'Projects in Automation Center',
      projects: [],
      auth: [],
      auth_text: ''
    }
  },

 created: function() {
    if (this.$store.state.token) {
      this.auth_text = "user already logged in ";
    }
    else {
      this.auth_text = "no user logged in";
    }
  },

  methods: {
    getProjects() {
      this.$http.get('http://127.0.0.1:8000/automation/api/projects/').then(response => {this.projects = response.data["results"], this.msg = 'get projects data from django api'});
    },

    logout: function () {
      this.$store.dispatch('logout')
      .then(() => {
        this.$router.push('/login')
      })
    }

  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

并添加一个login.vue文件作为登录用,login.vue会调用刚才store中的login方法,并修改全局的状态,以获取是否有登录。

// login.vue

<template>
 <div>
   <form class="login" @submit.prevent="login">
     <h1>Sign in</h1>
     <label>username</label>
     <input required v-model="username" type="text" placeholder="Name"/>
     <label>Password</label>
     <input required v-model="password" type="password" placeholder="Password"/>
     <button type="submit">Login</button>
   </form>
 </div>
</template>

<script>
export default {
  data () {
    return {
      username : "",
      password : ""
    };
  },

  methods: {
    login: function () {
      const { username, password } = this
      this.$store.dispatch('login', { username, password })
        .then(() => this.$router.push('/'))
        .catch(err => console.log(err))
    }
  }
};
</script>

此时需要再router/index.js中添加login的路由。

// router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/home'
import Login from '@/components/login'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    },
  ]
});

此时运行前端(npm run dev)和后端(python manage.py runserver)时,就能看到默认是未登录,也无法调用api。

但是登录后即使刷新页面,也会有token和登录状态的保留,不会丢失。

 

参考文章:

https://scotch.io/tutorials/handling-authentication-in-vue-using-vuex

https://blog.sqreen.com/authentication-best-practices-vue/

https://stackoverflow.com/questions/45384172/best-practice-for-storing-auth-tokens-in-vuejs

 

 

 

 

下一篇:  Django restframework加Vue打造前后端分离的网站(八)权限控制
上一篇:  为Django配置覆盖率检查并配置在gitlab上

共有0条评论

添加评论

暂无评论