Spring Security (三):与Vue.js整合

栏目: Java · 发布时间: 5年前

内容简介:本篇内容较长,先看下效果:然后侧边栏的路由其实根据我们后台获取到的:(这个数据结构比较简单,在这里只是演示)由于本人是个后端小辣鸡,前端对我来说简直就是地狱…就使用了

本篇内容较长,先看下效果:

Spring Security (三):与Vue.js整合

然后侧边栏的路由其实根据我们后台获取到的:(这个数据结构比较简单,在这里只是演示)

{
"code": 200,
"msg": "",
"data": {
    "id": "1",
    "username": "admin",
    "roles": [
        {
            "id": 1,
            "describe": null,
            "rolename": "ROLE_ADMIN",
            "permissions": null
        }
    ],
    "menus": [
        {
            "per_id": 1101,
            "per_paerent_id": 0,
            "per_name": "权限管理",
            "per_resource": "auth",
            "children": [
                {
                    "per_id": 1102,
                    "per_paerent_id": 1101,
                    "per_name": "角色管理",
                    "per_resource": "role",
                    "children": null
                },
                {
                    "per_id": 1103,
                    "per_paerent_id": 1101,
                    "per_name": "资源管理",
                    "per_resource": "per",
                    "children": null
                }
            ]
        }
      ]
    }
}
复制代码

由于本人是个后端小辣鸡,前端对我来说简直就是地狱…就使用了 GitHub 上的轮子试着改了一下。感谢 @PanJiaChen 的项目,我就使用了其中的模板来做的,如果有什么不完美的希望指出。

主要步骤

在前后端分离项目中我们前端只需要对数据进行渲染就ok了。目前后端已经可以成功给我们返回了用户信息,前端实现权限体系的主要思路是

  1. 首先就是查询用户具有的权限资源信息,然后我们前端根据这个信息,然后从所有的路由页面中动态的加载用户具有的路由页面。并且在没有访问权限的时候提示无权限。
  2. 由于Vue2.2.0以后新增了router.addRoutes,我们就可以通过这个来添加用户的路由即可~
  3. 本项目侧边栏和路由是一个文件,所以我们要按照一定的规则去设置路由。具体方法见:vue-element-admin

用户信息

首先修改login接口: src\api\login.js 修改为Spring Boot的接口。

import request from '@/utils/request'
/**
 * 登录操作
 * @param {用户名} username
 * @param {密码} password
 */
export function login(username, password) {
  return request({
    url: '/auth/login',
    method: 'post',
    data: {
      username,
      password
    }
  })
}
/**
 * 获取用户信息
 * @param {token} token
 */
export function getInfo(token) {
  return request({
    url: '/getUserInfo',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: 'user/logout',
    method: 'post'
  })
}
复制代码

修改 dev.env.js 为本地接口

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:8086"',
})
复制代码

修改 axios 配置文件

import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
if (store.getters.token) {
    // 这里修改为'jwtHeader'
  config.headers['jwtHeader'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
/**
 * code为非200是抛错 可结合自己业务进行修改
 */
const res = response.data
if (res.code !== 200) {
  Message({
    message: res.message,
    type: 'error',
    duration: 5 * 1000
  })

  // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
  if (res.code === 500 || res.code === 500 || res.code === 500) {
    MessageBox.confirm(
      '你已被登出,可以取消继续留在该页面,或者重新登录',
      '确定登出',
      {
        confirmButtonText: '重新登录',
        cancelButtonText: '取消',
        type: 'warning'
      }
    ).then(() => {
      store.dispatch('FedLogOut').then(() => {
        location.reload() // 为了重新实例化vue-router对象 避免bug
      })
    })
  }
  return Promise.reject('error')
} else {
  return response.data
}
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service
复制代码

以上都是最基本的连接配置。

路由配置

src\router\index.js

import Vue from 'vue'
import Router from 'vue-router'

// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading

Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

/**
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
*                                if not set alwaysShow, only more than one route under the children
*                                it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect           if `redirect:noredirect` will no redirect in the breadcrumb
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
* meta : {
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar
    breadcrumb: false            if false, the item will hidden in breadcrumb(default is true)
  }
**/

// 所有权限通用路由表
// 这里就是一些公共界面如,错误提示页面,登录页面是不需要权限的就可以在这个里面配置

export const constantRouterMap = [
  {
    path: '/login',
    name: 'Login',
    component: () =>
      import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    hidden: true,
    children: [{
      path: 'dashboard',
      component: () =>
        import('@/views/dashboard/index')
    }, {
      path: 'userinfo',
      name: 'UserInfo',
      component: () =>
        import('@/views/dashboard/userinfo')
    }]
  },
  {
    path: '*',
    redirect: '/404',
    hidden: true
  }
]

export default new Router({
  // mode: 'history', //后端支持可开
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})
// 异步挂载的路由
// 动态需要根据权限加载的路由表(这里的路由时用来动态加载的,通俗点讲就是需要权限控制的路由都在这个里面配置)
export const asyncRouterMap = [
  {
    path: '/auth',
    component: Layout,
    name: 'auth',
    meta: {
      resources: 'auth',
      title: '权限管理'
    },
    children: [
      {
        path: 'per',
        component: () => import('@/views/pre/perm/index'),
        name: 'per',
        meta: {
          resources: 'per'
        }
      },
      {
        path: 'user',
        component: () => import('@/views/pre/user/index'),
        name: 'user',
        meta: {
          resources: 'user'
        }
      },
      {
        path: 'role',
        component: () => import('@/views/pre/role/index'),
        name: 'role',
        meta: {
          resources: 'role'
        }
      }
    ]
  }
]
复制代码

然后你需要根据 import 的指令去添加相应的 Vue 文件。

权限控制

其实也就是拿着后台获取到的路由,然后和上面配置的路由进行判断,如果符合就加到用户的真实路由中。

1、首先修改src\store\modules\user.js

import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    username: '',
    user: {},
    roles: [], // 用户角色列表
    menus: [] // 菜单列表
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_INFO: (state, user) => {
      state.username = user.username
      state.user = user
    },
    SET_MENUS: (state, menus) => {
      state.menus = menus
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    }
  },

  actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        login(username, userInfo.password).then(res => {
          const data = res.data
          setToken(data)
          commit('SET_TOKEN', data)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(response => {
          const data = response.data
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', data.roles)
          } else {
            reject('getInfo: roles must be a non-null array !')
          }
          commit('SET_MENUS', data.menus)
          commit('SET_INFO', data)
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_INFO', '')
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user
复制代码

这里使用了 Vuex 进行状态管理,重点关注下 actions 中的 LoginGetInfo 方法, Login 则是登录之后获取到 token 然后把 token 存储,然后 GetInfo 就是拿着 token 去请求接口获取用户角色、权限资源信息了。

创建一个新文件 src\store\modules\permission.js

2、这个文件的作用就是处理路由,把从后台获取的路由和我们配置的 asyncRouterMap 进行匹配,然后就返回用户当前真实的路由了…也就是几个 forEach 然后把公共的和当前用户的路由 addRouters 就搞定了。

// store/permission.js
import { asyncRouterMap, constantRouterMap } from '@/router'

/**
 *
 * @param  {Array} userRouter 后台接口请求的路由
 * @param  {Array} allRouter  前端配置好的所有动态路由的集合
 * @return {Array} userRealRouters 过滤后的路由
 */

export function userCurrentRouter(userRouter = [], allRouter = []) {
  var userRealRouters = []
  allRouter.forEach((router, index) => {
    userRouter.forEach((item, index) => {
      // 拿用户的路由和配置路由进行匹配判断
      if (item.per_resource === router.meta.resources) {
        // 对路由下的子路由进行判断,递归添加
        if (item.children && item.children.length > 0) {
          router.children = userCurrentRouter(item.children, router.children)
        }
        // 这里是设置侧边栏的显示title还可以显示图标(没做)
        router.meta.title = item.per_name
        userRealRouters.push(router)
      }
    })
  })
  return userRealRouters
}
const permission = {
  state: {
    routers: constantRouterMap,
    apiRouters: [] // 后台接口获取得到的路由(per_resource)
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.apiRouters = routers
      state.routers = constantRouterMap.concat(routers)
    }
  },
  actions: {
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        commit('SET_ROUTERS', userCurrentRouter(data, asyncRouterMap))
        resolve()
      })
    }
  }
}

export default permission
复制代码

重点关注下 userCurrentRouterGenerateRoutes ,第一个是匹配路由,第二个是把公共的路由和真实路由匹配到一起。最主要的是理解 Vuex 的意义。

3、此外修改一下src\store\getters.js文件

const getters = {
  sidebar: state => state.app.sidebar,
  token: state => state.user.token,
  username: state => state.user.username,
  roles: state => state.user.roles,
  user: state => state.user.user,
  menus: state => state.user.menus,
  menu: state => state.permission.routers,
  apiRouters: state => state.permission.apiRouters
}
export default getters
复制代码

其实也就是你想要哪些信息,然后在里面定义好,然后我们就可以全局从 Vuex 中拿了,是不是很方便!

4、修改src\store\index.js

import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import permission from './modules/permission'
import getters from './getters'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    user,
    permission
  },
  getters
})

export default store
复制代码

5、然后重要的一步来了,src\permission.js

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth' // 验权

const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(res => {
          // 生成可访问的路由表
          store.dispatch('GenerateRoutes', store.getters.menus).then(r => {
            // 动态添加可访问路由表
            router.addRoutes(store.getters.apiRouters)
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 结束Progress
})
复制代码

这个就是判断是否正确的拿到路由信息然后会给我们生成路由。

6、修改src\views\layout\components\Sidebar\index.vue

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">


    <el-menu
      :show-timeout="200"
      :default-active="$route.path"
      :collapse="isCollapse"
      mode="vertical"
      background-color="#304156"
      text-color="#bfcbd9"
      active-text-color="#409EFF"
    >
      <sidebar-item v-for="route in menu" :key="route.path" :item="route" :base-path="route.path"/>
    </el-menu>
  </el-scrollbar>
</template>

<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'

export default {
  components: {
    SidebarItem
  },
  computed: {
    ...mapGetters([
      'menu',
      'sidebar'
    ]),
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>
复制代码

v-for 指令放到 sideBar 中去,目前为止基本动态的路由菜单已经可以生成了。如果有什么问题,请联系我指正…感谢…代码已经同步到 GitHub

Getting started

# clone the project
git clone https://github.com/ywbjja/Vue_templete.git

# install dependency
npm install

# develop
npm run dev
复制代码

以上所述就是小编给大家介绍的《Spring Security (三):与Vue.js整合》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Speed Up Your Site

Speed Up Your Site

Andrew B. King / New Riders Press / 2003-01-14 / USD 39.99

There's a time bomb on the web: user patience. It starts ticking each time someone opens one of your pages. You only have a few seconds to get compelling content onto the screen. Fail, and you can kis......一起来看看 《Speed Up Your Site》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具