从根本上讲前端仅仅只是视图层的展示, 权限的核⼼是在于服务器中的数据变化, 所以后端才是权限的关键, 后端权限可以控制某个⽤户是否能够查询数据, 是否能够修改数据等操作
cookie
session
token
⽤户
⻆⾊
权限
前端权限的控制本质上来说, 就是控制前端的 视图层的展示和前端所发送的请求. 但是只有前端权限控制没有后端权限控制是万万不可的. 前端权限控制只可以说是达到锦上添花的效果.
如果仅从能够修改服务器中数据库中的数据层⾯上讲,确实只在后端做控制就⾜够了, 那为什么越来越多的项⽬也进⾏了前端权限的控制, 主要有这⼏⽅⾯的好处
就算点击了也最终会失败
的按钮, 势必会增加有⼼者在登录请求中, 会得到权限数据, 当然, 这个需要后端返回数据的⽀持. 前端根据权限数据, 展示对应的菜单.点击菜单,才能查看相关的界⾯.
如果⽤户没有登录,⼿动在地址栏敲⼊管理界⾯的地址, 则需要跳转到登录界⾯如果⽤户已经登录, 可是⼿动敲⼊⾮权限内的地址, 则需要跳转404界⾯
在某个菜单的界⾯中, 还得根据权限数据, 展示出可进⾏操作的按钮, ⽐如删除,修改,增加
如果⽤户通过⾮常规操作, ⽐如通过浏览器调试⼯具将某些禁⽤的按钮变成启⽤状态, 此时发的请求, 也应该被前端所拦截
{
"data": {
"id": 500,
"rid": 0, "username": "admin",
"mobile": "13999999999",
"email": ["[email protected]"](mailto:123999@qq.com), "token": "Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1M TI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHm- tPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM"
},
"rights": [{
"id": 125,
"authName": "⽤户管理",
"icon": "icon-user",
"children": [{
"id": 110,
"authName": "⽤户列表",
"path": "users",
"rights": ["view", "edit", "add", "delete"]
}]
}, {
"id": 103,
"authName": " ⻆ ⾊ 管 理 ", "icon": "icon-tijikongjian", "children": [{
"id": 111,
"authName": "⻆⾊列表",
"path": "roles",
"rights": ["view", "edit", "add", "delete"]
}]
}, {
"id": 101,
"authName": "商品管理",
"icon": "icon-shangpin",
"children": [{
"id": 104,
"authName": "商品列表",
"path": "goods",
"rights": ["view", "edit", "add", "delete"]
}, {
"id": 121,
"authName": "商品分类", "path": "categories",
"rights": ["view", "edit", "add", "delete"]
}]
}],
"meta": {
"msg": "登录成功", "status": 200
}
}
在这部分数据中, 除了该⽤户的基本信息之外, 还有两个字段很关键
export default new Vuex.Store({ state: {
rightList:[]
},
mutations: { setRightList(state, data) {
state.rightList = data
}
},
actions: {
},
getters: {
}
})
login() {
this.$refs.loginFormRef.validate(async valid => {
......
this.$store.commit('setRightList', res.rights) this.$message.success(' 登 录 成 功 ') this.$router.push('/home')
})
}
import { mapState } from 'vuex' computed: {
...mapState(['rightList'])
}
created() {
this.activePath = window.sessionStorage.getItem('activePath') this.menulist = this.rightList
},
因为菜单数据是登录之后才获取到的, 获取菜单数据之后,就存放在Vuex中 ⼀旦刷新界⾯, Vuex中的数据会重新初始化, 所以会变成空的数组 因此, 需要将权限数据存储在sessionStorage中, 并让其和Vuex中的数据保持同步
export default new Vuex.Store({ state: {
rightList:JSON.parse(sessionStorage.getItem('rightList')||'[]')
},
mutations: { setRightList(state, data) {
state.rightList = data sessionStorage.setItem('rightList',JSON.stringify(data))
}
},
actions: {
},
getters: {
}
})
export default new Vuex.Store({ state: {
rightList:JSON.parse(sessionStorage.getItem('rightList')||'[]'), username: sessionStorage.getItem('username')
},
mutations: { setRightList(state, data) {
state.rightList = data sessionStorage.setItem('rightList',JSON.stringify(data))
},
setUsername(state, data) { state.username = data
sessionStorage.setItem('username',data)
}
},
actions: {
},
getters: {
}
})
login() {
this.$refs.loginFormRef.validate(async valid => {
......
this.$store.commit('setRightList', res.rights) this.$store.commit('setUsername', res.data.username) this.$message.success(' 登 录 成 功 ') this.$router.push('/home')
})
}
computed: {
...mapState(['rightList','username'])
}
<el-button type="info" @click="logout">{{username}}退出</el-button>
logout() {
sessionStorage.clear() this.$router.push('/login') window.location.reload()
},
sessionStorage.setItem('token', res.data.token)
router.beforeEach((to, from, next) => { if (to.path === '/login') {
next()
} else {
const token = sessionStorage.getItem('token') if(!token) {
next('/login')
} else {
next()
}
}
})
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login.vue' import Home from '@/components/Home.vue' import Welcome from '@/components/Welcome.vue'
import Users from '@/components/user/Users.vue' import Roles from '@/components/role/Roles.vue'
import GoodsCate from '@/components/goods/GoodsCate.vue' import GoodsList from '@/components/goods/GoodsList.vue' import NotFound from '@/components/NotFound.vue'
import store from '@/store' Vue.use(Router)
const userRule = { path: '/users', component: Users } const roleRule = { path: '/roles', component: Roles } const goodsRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
const ruleMapping = { 'users': userRule, 'roles': roleRule, 'goods': goodsRule, 'categories': categoryRule
}
const router = new Router({ routes: [
{
path: '/', redirect: '/home'
},
{
path: '/login', component: Login
},
{
path: '/home', component: Home, redirect: '/welcome', children: [
{ path: '/welcome', component: Welcome },
// { path: '/users', component: Users },
// { path: '/roles', component: Roles },
// { path: '/goods', component: GoodsList },
// { path: '/categories', component: GoodsCate }
]
},
{
path: '*', component: NotFound
}
]
})
router.beforeEach((to, from, next) => {
if (to.path === '/login') { next()
} else {
const token = sessionStorage.getItem('token') if(!token) {
next('/login')
} else {
next()
}
}
})
export function initDynamicRoutes() {
const currentRoutes = router.options.routes const rightList = store.state.rightList rightList.forEach(item => {
item.children.forEach(item => { currentRoutes[2].children.push(ruleMapping[item.path])
})
})
router.addRoutes(currentRoutes)
}
export default router
import { initDynamicRoutes } from '@/router.js' login() {
this.$refs.loginFormRef.validate(async valid => { if (!valid) return
const { data: res } = await this.$http.post('login', this.loginForm)
if (res.meta.status !== 200) return this.$message.error('登录失败!')
this.$store.commit('setRightList', res.rights)
this.$store.commit('setUsername', res.data.username) sessionStorage.setItem('token', res.data.token) initDynamicRoutes()
this.$message.success('登录成功')
this.$router.push('/home')
})
}
import { initDynamicRoutes } from '@/router.js' export default {
name: 'app', created() {
initDynamicRoutes()
}
}
按钮控制
虽然⽤户可以看到某些界⾯了, 但是这个界⾯的⼀些按钮,该⽤户可能是没有权限的.因此, 我们需要对组件中的⼀些按钮进⾏控制. ⽤户不具备权限的按钮就隐藏或者禁⽤, ⽽在这块中, 可以把该逻辑放到⾃定义指令中
import Vue from 'vue'
import router from '@/router.js' Vue.directive('permission', {
inserted: function(el, binding){ const action = binding.value.action
const currentRight = router.currentRoute.meta if(currentRight) {
if(currentRight.indexOf(action) == -1) {
// 不具备权限
const type = binding.value.effect if(type === 'disabled') {
el.disabled = true el.classList.add('is-disabled')
} else { el.parentNode.removeChild(el)
}
}
}
}
})
import './utils/permission.js'
export function initDynamicRoutes() {
const currentRoutes = router.options.routes const rightList = store.state.rightList rightList.forEach(item => {
item.children.forEach(item => {
const itemRule = ruleMapping[item.path] itemRule.meta = item.rights currentRoutes[2].children.push(itemRule)
})
})
router.addRoutes(currentRoutes)
}
v-permission="{action:'add'}"
v-permission="{action:'delete', effect:'disabled'}"
axios.interceptors.request.use(function(req){ const currentUrl = req.url
if(currentUrl !== 'login') {
req.headers.Authorization = sessionStorage.getItem('token')
}
return req
})
import axios from 'axios' import Vue from 'vue'
import router from '../router'
// 配置请求的跟路径, ⽬前⽤mock模拟数据, 所以暂时把这⼀项注释起来
// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/' const actionMapping = {
get: 'view',
post: 'add',
put: 'edit', delete: 'delete'
}
axios.interceptors.request.use(function(req){ const currentUrl = req.url
if(currentUrl !== 'login') {
req.headers.Authorization = sessionStorage.getItem('token')
// 当前模块中具备的权限
// 查看 get请求
// 增加 post请求
// 修改 put请求
// 删除 delete请求
const method = req.method
// 根据请求, 得到是哪种操作
const action = actionMapping[method]
// 判断action是否存在当前路由的权限中
const rights = router.currentRoute.meta if(rights && rights.indexOf(action) == -1) {
// 没有权限
alert('没有权限')
return Promise.reject(new Error('没有权限'))
}
}
return req
})
axios.interceptors.response.use(function(res){ return res
})
Vue.prototype.$http = axios
axios.interceptors.response.use(function(res){ if (res.data.meta.status === 401) {
router.push('/login') sessionStorage.clear() window.location.reload()
}
return res
})
前端权限的实现必须要后端提供数据⽀持, 否则⽆法实现.
返回的权限数据的结构,前后端需要沟通协商, 怎样的数据使⽤起来才最⽅便.
权限的数据需要在多组件之间共享, 因此采⽤vuex
防⽌刷新界⾯,权限数据丢失, 所以需要存储在sessionStorage, 并且要保证两者的同步
路由的导航守卫可以防⽌跳过登录界⾯
动态路由可以让不具备权限的界⾯的路由规则压根就不存在
路由规则中可以增加路由元数据meta
通过路由对象可以得到当前的路由规则,以及存储在此规则中的meta数据
⾃定义指令可以很⽅便的实现按钮控制