- 权限管理
- 权限管理概念和方法
- 权限管理是什么
- 前端如何做权限控制
- 接口权限
- 路由权限
- 方案一
- 方案二
- 菜单权限
- 方案一
- 方案二
- 按钮权限
- 方案一
- 方案二
- 小结
权限管理
权限管理概念和方法
权限管理是指对系统、应用程序或数据资源的访问和使用进行控制和管理的过程。
它确保只有经过授权的用户能够获得适当的权限来执行特定的操作或访问敏感的信息。
权限管理在信息系统安全和数据保护方面起着重要的作用。
在权限管理中,以下是一些常见的概念和方法:
-
身份验证(Authentication):身份验证是确认用户身份的过程。通过用户名和密码、指纹、智能卡或其他认证方式,系统可以验证用户是否是其所声称的身份。
-
授权(Authorization):授权是系统根据用户的身份和角色,确定用户能够执行的操作和访问的资源。授权通常基于预定义的权限策略和规则来进行。
-
角色管理(Role Management):角色是一组具有相似权限需求的用户的集合。通过将用户分配给角色,可以简化权限管理,并确保用户只获得其所需的最低权限。
-
权限继承(Permission Inheritance):权限继承是一种层级结构的权限管理方法,其中较高级别的实体(如组织单位)拥有更高级别的权限,而较低级别的实体(如个人用户)继承这些权限。
-
最小权限原则(Principle of Least Privilege):最小权限原则指的是给予用户执行任务所需的最低权限,以减少潜在的安全风险。这意味着用户只能访问和操作他们工作所需的资源,而不会被授予超过必要的权限。
-
审计与监控(Auditing and Monitoring):审计功能可以跟踪和记录用户对系统和资源的访问日志。监控功能可实时监控用户的行为,并及时检测异常活动或未经授权的访问。
-
定期审查(Regular Review):定期审查权限设置和用户权限是一项重要的管理措施,以确保权限与用户的实际需求保持一致,并防止权限的滥用和泄露。
权限管理是确保系统和数据资源安全和保护的关键措施,通过身份验证、授权、角色管理等方法来管理用户的权限,以及通过审计和监控来追踪和防止未经授权的访问。
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
权限管理是什么
权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源
而前端权限归根结底是 请求的发起权,请求的发起可能有下面两种形式触发
- 页面加载触发
- 页面上的按钮点击触发
所有的请求发起都触发自前端路由或视图
所以我们可以从这两方面入手,对触发权限的源头进行控制,最终要实现的目标是:
- 路由方面,用户登录后只能看到自己有权访问的导航菜单,也只能访问自己有权访问的路由地址.否则将跳转 4xx 提示页
- 视图方面,用户只能看到自己有权浏览的内容和有权操作的控件
- 最后再加上 请求控制 作为最后一道防线,路由可能配置失误,按钮可能忘了加权限,这种时候请求控制可以用来兜底,越权请求将在前端被拦截
前端如何做权限控制
前端权限控制可以分为四个方面
- 接口权限
- 按钮权限
- 菜单权限
- 路由权限
接口权限
接口权限目前一般采用 jwt 的形式来验证,没有通过的话一般返回 401 ,跳转到登录页面重新进行登录
登录完拿到 token ,将 token 存起来,通过 axios 请求拦截器进行拦截,每次请求的时候头部携带 token
axios.interceptors.request.use(config => {config.headers['token'] = cookie.get('token')return config
})axios.interceptors.response.use(res=>{},{response}=>{if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误router.push('/login')}
})
路由权限
方案一
初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验
const routerMap = [{path: '/permission',component: Layout,redirect: '/permission/index',alwaysShow: true, // will always show the root menumeta: {title: 'permission',icon: 'lock',roles: ['admin', 'editor'] // you can set roles in root nav},children: [{path: 'page',component: () => import('@/views/permission/page'),name: 'pagePermission',meta: {title: 'pagePermission',roles: ['admin'] // or you can only set roles in sub nav}}, {path: 'directive',component: () => import('@/views/permission/directive'),name: 'directivePermission',meta: {title: 'directivePermission'// if do not set roles, means: this page does not require permission}}]}
]
这种方式存在以下四种缺点:
- 加载所有的路由,如果路由很多,而用户并不是所有的路由都有权限访问,对性能会有影响。
- 全局路由守卫里,每次路由跳转都要做权限判断。
- 菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
- 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
方案二
初始化的时候先挂载不需要权限控制的路由,比如登录页,404等错误页。
如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制
登录后,获取用户的权限信息,然后筛选有权限访问的路由,在全局路由守卫里进行调用 addRoutes 添加路由
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookieNProgress.configure({ showSpinner: false })// NProgress Configuration// permission judge function
function hasPermission(roles, permissionRoles) {if (roles.indexOf('admin') >= 0) return true // admin permission passed directlyif (!permissionRoles) return truereturn roles.some(role => permissionRoles.indexOf(role) >= 0)
}const whiteList = ['/login', '/authredirect']// no redirect whitelistrouter.beforeEach((to, from, next) => {NProgress.start() // start progress barif (getToken()) { // determine if there has token/* has token*/if (to.path === '/login') {next({ path: '/' })NProgress.done() // if current page is dashboard will not trigger af terEach hook, so manually handle it} else {if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息store.dispatch('GetUserInfo').then(res => { // 拉取user_info信息const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop'] store.dispatch('GenerateRoutes', { roles }).then(() => { //根据 roles权限生成可访问的路由表 router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 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() 删除下方权限判断 ↓if (hasPermission(store.getters.roles, to.meta.roles)) {next()//} else {next({ path: '/401', replace: true, query: { noGoBack: true }})}// 可删 ↑}}
} else {/* has no token*/if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入next()} else {next('/login') // 否则全部重定向到登录页NProgress.done() // if current page is login will not trigger afterE ach hook, so manually handle it}
}
})router.afterEach(() => {NProgress.done() // finish progress bar
})
按需挂载,路由就需要知道用户的路由权限,也就是在用户登录进来的时候就要知道当前用户拥有哪些路由权限
这种方式也存在了以下的缺点:
- 全局路由守卫里,每次路由跳转都要做判断
- 菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
- 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
菜单权限
菜单权限可以理解成将页面与理由进行解耦
方案一
菜单与路由分离,菜单由后端返回, 前端定义路由信息
{name: "login",path: "/login",component: () => import("@/pages/Login.vue")
}
name 字段都不为空,需要根据此字段与后端返回菜单做关联,后端返回的菜单信息中必须要有 name 对应的字段,并且做唯一性校验
全局路由守卫里做判断
function hasPermission(router, accessMenu) {if (whiteList.indexOf(router.path) !== -1) {return true;}let menu = Util.getMenuByName(router.name, accessMenu);if (menu.name) {return true;}return false;
}Router.beforeEach(async (to, from, next) => {if (getToken()) {let userInfo = store.state.user.userInfo;if (!userInfo.name) {try {await store.dispatch("GetUserInfo")await store.dispatch('updateAccessMenu')if (to.path === '/login') {next({ name: 'home_index' })} else {//Util.toDefaultPage([...routers], to.name, router, next);next({ ...to, replace: true }) // 菜单权限更新完成,重新进一次当前路由}} catch (e) {if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入next()} else {next('/login')}}} else {if (to.path === '/login') {next({ name: 'home_index' })} else {if (hasPermission(to, store.getters.accessMenu)) {Util.toDefaultPage(store.getters.accessMenu,to, routes, next);} else {next({ path: '/403',replace:true })}}}} else {if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next()} else {next('/login')}}let menu = Util.getMenuByName(to.name, store.getters.accessMenu);Util.title(menu.title);
});Router.afterEach((to) => {window.scrollTo(0, 0);
});
方案二
菜单和路由都由后端返回
前端统一定义路由组件
const Home = () => import("../pages/Home.vue");
const UserInfo = () => import("../pages/UserInfo.vue");export default {home: Home,userInfo: UserInfo
};
后端路由组件返回以下格式
[{name: "home",path: "/",component: "home"},{name: "home",path: "/userinfo",component: "userInfo"}
]
在将后端返回路由通过 addRoutes 动态挂载之间,需要将数据处理一下,将 cmponent 字段换为真正的组件
如果有嵌套路由,后端功能设计的时候,要注意添加相应的字段,前端拿到数据也要做相应的处理
这种方法也会存在缺点:
- 全局路由守卫里,每次路由跳转都要做判断
- 前后端的配合要求更高
按钮权限
方案一
按钮权限也可以用 v-if 判断
但是如果页面过多,每个页面页面都要获取用户权限 role 和路由表里的 meta.btnPermissions ,然后再做判断
具体情况如下:
在Vue中,你可以使用路由表中的meta.btnPermissions
来进行按钮权限控制。meta.btnPermissions
是一个对象,其中包含用于确定按钮是否可见或可用的权限信息。
下面是一个示例,展示了如何使用路由表中的meta.btnPermissions
来控制按钮的权限:
- 首先,确保你已经安装并配置了Vue Router。
- 在你的路由表中,为每个路由添加
meta.btnPermissions
字段。例如:
const routes = [{path: '/dashboard',component: Dashboard,meta: {btnPermissions: {view: true,edit: false,delete: false,},},},{path: '/users',component: Users,meta: {btnPermissions: {view: true,edit: true,delete: true,},},},// 其他路由...
];
在上述示例中,每个路由都有一个meta.btnPermissions
字段,其中包含了该路由相关的按钮权限信息。
你可以根据实际需求设置每个按钮的权限。
- 在你的组件中,使用
this.$router
来访问路由表,并根据当前路由的meta.btnPermissions
来控制按钮的显示和可用性。例如:
<template><div><button v-if="checkButtonPermissions('view')">查看</button><button v-if="checkButtonPermissions('edit')">编辑</button><button v-if="checkButtonPermissions('delete')">删除</button></div>
</template><script>
export default {methods: {checkButtonPermissions(permission) {const route = this.$router.currentRoute; // 获取当前路由对象const meta = route.meta.btnPermissions; // 获取当前路由的meta.btnPermissions对象return meta[permission]; // 返回指定权限的值},},
};
</script>
我们通过this.$router.currentRoute
获取当前路由对象,然后从路由对象的meta.btnPermissions
字段中获取按钮权限信息。
最后,通过checkButtonPermissions
方法判断按钮是否可见或可用。你可以根据实际需求调整该方法的实现。
方案二
通过自定义指令进行按钮权限的判断
首先配置路由
{path: '/permission',component: Layout,name: '权限测试',meta: {btnPermissions: ['admin', 'supper', 'normal'] // 页面需要的权限}, children: [{path: 'supper',component: _import('system/supper'),name: '权限测试',meta: {btnPermissions: ['admin', 'supper']} // 页面需要的权限},{path: 'normal',component: _import('system/normal'),name: '权限测试',meta: {btnPermissions: ['admin']} // 页面需要的权限}]
}
自定义权限鉴定指令
import Vue from 'vue'/**权限指令**/
const has = Vue.directive('has', {bind: function (el, binding, vnode) {// 获取页面按钮权限let btnPermissionsArr = [];if(binding.value){// 如果指令传值,获取指令参数,根据指令参数和当前登录人按钮权限做比较。btnPermissionsArr = Array.of(binding.value);}else{// 否则获取路由中的参数,根据路由的btnPermissionsArr和当前登录人按钮权限做比较。btnPermissionsArr = vnode.context.$route.meta.btnPermissions;}if (!Vue.prototype.$_has(btnPermissionsArr)) {el.parentNode.removeChild(el);}}
});// 权限检查方法
Vue.prototype.$_has = function (value) {let isExist = false;// 获取用户按钮权限let btnPermissionsStr = sessionStorage.getItem("btnPermissions");if (btnPermissionsStr == undefined || btnPermissionsStr == null) {return false;}if (value.indexOf(btnPermissionsStr) > -1) {isExist = true;}return isExist;
};export {has}
在使用的按钮中只需要引用 v-has 指令
<el-button @click='editClick' type="primary" v-has>编辑</el-button>
小结
关于权限如何选择哪种合适的方案,可以根据自己项目的方案项目,如考虑路由与菜单是否分离
权限需要前后端结合,前端尽可能的去控制,更多的需要后台判断