vue+springmvc前后端分离开发(十五)(在前端使用oauth2)

前情提要

  • 上一节完成了oauth2的后端实现,已经可以使用postman向接口发送请求来获取token了
  • 这一节会讲解如何在前端实现token存储、token认证等与后端交互的逻辑

qs模块

  • 要使用oauth2,首先需要安装qs依赖,qs依赖是用来解决当请求头不是json形式时,数据发送不匹配的问题,而向oauth2发送请求的格式时x-www-form-urlencoded格式的
  • 直接使用vue ui来安装即可
    vue+springmvc前后端分离开发(十五)(在前端使用oauth2)_第1张图片

vue-cookies

  • 在用户登录之后,经常需要存储token,所以要把vue-cookies依赖添加进来,前面的章节已经添加过vue-cookies依赖了,现在需要在main.js中以Vue.use()的方式使用它
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import axios from 'axios'
import VueAxios from 'vue-axios'
import VueCookies from 'vue-cookies'

// 使用Vue.use的方式装载组件可以不破坏Vue的原型属性
Vue.use(VueAxios, axios)

// 使用vue.use添加vue-cookies
Vue.use(VueCookies)

Vue.config.productionTip = false

new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app')

修改vuex的用户状态

  • 因为oauth2是使用token进行认证的,所以需要略微调整用户的全局状态以适应该模式,将store/index.js文件中的内容修改为如下形式(代码很长,建议自行理解每一个设置的意义)
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    // 获取部分请求需要用到的认证,目前是access token
    accessToken: '',
    refreshToken: '',
    // 代表用户自身,最终不一定会以这种方式呈现
    user: {
      id: null,
      username: '',
      password: '',
      enabled: true,
      firstName: '',
      lastName: '',
      gender: 0,
      phone: '',
      email: '',
      icon: '',
      birthday: new Date().toISOString().substr(0, 10),
      joinedDate: new Date().toISOString().substr(0, 10),
      // 和用户相关的一些链接,比如用户自身的描述
      _links: []
    }
  },
  mutations: {
    // 初始化用户
    initUser (state) {
      state.user = {
        id: null,
        username: '',
        password: '',
        enabled: true,
        firstName: '',
        lastName: '',
        gender: 0,
        phone: '',
        email: '',
        icon: '',
        birthday: new Date().toISOString().substr(0, 10),
        joinedDate: new Date().toISOString().substr(0, 10),
        // 和用户相关的一些链接,比如用户自身的描述
        _links: []
      }
    },
    // 登录后设置user
    setUser (state, user) {
      // 设置用户信息
      state.user = user
      // 将用户生日修改为正确格式
      state.user.birthday = state.user.birthday == null ? null : state.user.birthday.substr(0, 10)
      // 将user存储在cookies中
      Vue.$cookies.set('user', state.user, 7200)
    },
    // 注销后清除user
    removeUser (state) {
      this.commit('initUser')
      // 清除cookies
      if (Vue.$cookies.isKey('user')) {
        Vue.$cookies.remove('user')
      }
    },
    // 初始化token
    initToken (state) {
      state.accessToken = ''
      state.refreshTOken = ''
    },
    // 设置token
    setToken (state, data) {
      state.accessToken = data.access_token
      state.refreshToken = data.refresh_token
      Vue.$cookies.set('accessToken', state.accessToken, 7200)
      Vue.$cookies.set('refreshToken', state.refreshToken, 86400)
    },
    // 清除token
    removeToken (state) {
      this.commit('initToken')
      // 清除缓存
      if (Vue.$cookies.isKey('accessToken')) {
        Vue.$cookies.remove('accessToken')
      }
      if (Vue.$cookies.isKey('refreshToken')) {
        Vue.$cookies.remove('refreshToken')
      }
    },
    // 页面加载时获取用户
    loadUser (state) {
      if (Vue.$cookies.isKey('user')) {
        state.user = Vue.$cookies.get('user')
        // 同时获取accessToken和refreshToken
        if (Vue.$cookies.isKey('accessToken')) {
          state.accessToken = Vue.$cookies.get('accessToken')
        }
        if (Vue.$cookies.isKey('refreshToken')) {
          state.refreshToken = Vue.$cookies.get('refreshToken')
        }
      } else if (Vue.$cookies.isKey('refreshToken')) {
        // access token过期,但是可以刷新token
        const refreshToken = Vue.$cookies.get('refreshToken')
        const qs = require('qs')
        const data = qs.stringify({
          refresh_token: refreshToken,
          client_id: 'kmhc',
          client_secret: '123456',
          grant_type: 'refresh_token'
        })
        Vue.axios.post('http://127.0.0.1:9001/oauth/token', data)
          .then(response => {
            // 成功刷新令牌,调用checkAccessToken获取用户信息
            this.commit('setToken', response.data)
            this.commit('checkAccessToken', response.data.access_token)
          })
      } else {
        this.commit('initUser')
      }
    },
    checkAccessToken (state, accessToken) {
      // 首先检查access_token的有效性
      Vue.axios.get('http://127.0.0.1:9001/oauth/check_token', {
        params: {
          token: accessToken
        }
      }).then(response => {
        // 请求成功,判断active是否为true,获取username
        if (response.data.active) {
          const username = response.data.user_name
          // 继续发送请求,获取具体用户信息
          Vue.axios.get('http://127.0.0.1:9001/api/users/search/findByUsername', {
            params: {
              username: username
            }
          }).then(response => {
            this.commit('setUser', response.data)
          })
        }
      })
    }
  },
  actions: {
    // 设置user的异步方法,可以明显的感觉到延迟降低
    setUser (context, user) {
      context.commit('setUser', user)
    },
    // 设置token的异步方法
    setToken (context, data) {
      context.commit('setToken', data)
    }
  },
  modules: {
  }
})
  • 修改完以后,就可以在Login.vue中完成点击登录,设置用户了

修改Login.vue

  • 实际上只需要修改login()这个函数即可
// 登录功能
login () {
  // 首先验证表单填写的正确性
  const valid = this.$refs.loginForm.validate()
  if (valid) {
    // 向授权服务器发送请求,获取access token,请求时x-www-form-urlencoded格式
    const qs = require('qs')
    const data = qs.stringify({
      client_id: 'kmhc',
      client_secret: '123456',
      grant_type: 'password',
      username: this.form.username,
      password: this.form.password
    })
    this.axios.post('http://127.0.0.1:9001/oauth/token', data)
      .then(response => {
        // 授权成功,设置token
        this.$store.commit('setToken', response.data)
        // 并且获取用户详情,这里不需要验证access token以获取用户名,因为用户名已有
        this.axios.get('http://127.0.0.1:9001/api/users/search/findByUsername', {
          params: {
            username: this.form.username
          }
        }).then(response => {
          // 异步设置user
          this.$store.dispatch('setUser', response.data)
          // 设置完成后,跳转到主页
          this.$router.push({
            name: 'Home'
          })
        }).catch(error => {
          console.log(error)
          // 用户认证错误,显示消息条
          this.snackbar = true
        })
      })
  }
}

测试oauth2登录

  • login()逻辑写完,用户状态修改完毕,是时候测试oauth2了,和以前一样,只要点击登录按钮就行
    vue+springmvc前后端分离开发(十五)(在前端使用oauth2)_第2张图片
  • 在浏览器上用vue-devtools查看vuex中的内容,可以发现确实交互成功,accessToken、refreshToken以及user都成功设置

打开页面时加载user

  • 为了能够不重复登录,需要在项目中添加初次打开网站时就加载全局user的逻辑,在store/index.js中已经编写了loadUser()这个方法了,我们只需要在App.vue这个文件创建时调用它即可
  • 在App.vue的script中添加如下代码
<script>

export default {
  name: 'App',

  data: () => ({
    // 控制导航侧栏的显示和隐藏
    drawer: true
  }),
  methods: {
    // 通过视图的名字跳转到特定视图,一般用作主页,登录页,注册页等
    open (name) {
      // 需要先判断当前路由名称是否和目标路由一致,如果一致就不跳转
      if (this.$route.name !== name) {
        this.$router.push({
          name: name
        })
      }
    },
    // 注销
    logout () {
      this.$store.commit('removeUser')
    }
  },
  created () {
    // 初始化时加载user
    this.$store.commit('loadUser')
  }
}
script>
  • 保存文件,此时刷新页面,可以看见user依然存在

在请求头部添加token

  • 写了这么多,如果不能使用oauth2来完成权限认证的话就没有意义,所以需要在每次向后端发送请求的时候在头部添加Authentication
  • 在main.js中添加如下代码(这里的主要逻辑就是拦截器的使用)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import axios from 'axios'
import VueAxios from 'vue-axios'
import VueCookies from 'vue-cookies'

// 使用Vue.use的方式装载组件可以不破坏Vue的原型属性
Vue.use(VueAxios, axios)

// 使用vue.use添加vue-cookies
Vue.use(VueCookies)

Vue.config.productionTip = false

Vue.axios.interceptors.request.use(
  function (config) {
    if (Vue.$cookies.isKey('accessToken')) {
      config.headers.Authorization = 'Bearer ' + Vue.$cookies.get('accessToken')
    }
    return config
  },
  function (error) {
    return Promise.reject(error)
  }
)

new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app')
  • 配置完毕以后,以后的请求都会添加这么一个Authorization字段,并且使用Bearer认证模式

至此,在前端使用oauth2已讲解完毕,下一节开始会讲解如何搭建一个用户管理的后台

你可能感兴趣的:(前后端分离开发,vue,oauth2)