1 前端权限控制

1.1 需求分析

1.1.1 需求说明

基于前后端分离的开发模式中,权限控制分为前端页面可见性权限与后端API接口可访问行权限。前端的权限控制主要围绕在菜单是否可见,以及菜单中按钮是否可见两方面展开的。

1.1.2 实现思路

在vue工程中,菜单可以简单的理解为vue中的路由,只需要根据登录用户的权限信息动态的加载路由列表就可以动态的构造出访问菜单。


  1. 登录成功后获取用户信息,包含权限列表(菜单权限,按钮权限)
  2. 根据用户菜单权限列表,动态构造路由(根据路由名称和权限标识比较)
  3. 页面按钮权限通过自定义方法控制可见性

SaaS-前端权限控制_权限控制

1.2 服务端代码实现

对系统微服务的FrameController的profile方法(获取用户信息接口)进行修改,添加权限信息

/**
* 获取个人信息
*/
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public Result profile(HttpServletRequest request) throws Exception {
//请求中获取key为Authorization的头信息
String authorization = request.getHeader("Authorization");
if(StringUtils.isEmpty(authorization)) {
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
//前后端约定头信息内容以 Bearer+空格+token 形式组成
String token = authorization.replace("Bearer ", "");
//比较并获取claims
Claims claims = jwtUtil.parseJWT(token);
if(claims == null) {
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
//查询用户
User user = userService.findById(userId);
ProfileResult result = null;
if("user".equals(user.getLevel())) {
result = new ProfileResult(user);
}else {
Map map = new HashMap();
if("coAdmin".equals(user.getLevel())) {
map.put("enVisible","1");
}
List<Permission> list = permissionService.findAll(map);
result = new ProfileResult(user,list);
}
return new Result(ResultCode.SUCCESS,result);
}

1.3 前端代码实现

1.3.1 路由钩子函数

vue路由提供的钩子函数(beforeEach)主要用来在加载之前拦截导航,让它完成跳转或取消。可以在路由钩子函数中进行校验是否对某个路由具有访问权限

router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
if (getToken()) {
// determine if there has token
/* has token */
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('GetUserInfo')
.then(res => {
// 拉取user_info
const roles = res.data.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(() => {
store.dispatch('FedLogOut').then(() => {
Message.error('验证失败, 请重新登录')
next({path: '/login'})
})
})
} else {
next()
}
}
} else {
/* has no token */
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next('/login') // 否则全部重定向到登录页
NProgress.done() // if current page is login will not trigger afterEach hook, so
manually handle it
}
}
})

1.3.2 配置菜单权限

在 ​​\src\module-dashboard\store\permission.js​​ 下进行修改,开启路由配置

actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { roles } = data
//动态构造权限列表
let accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
commit('SET_ROUTERS', accessedRouters)
//commit('SET_ROUTERS', asyncRouterMap) // 调试开启全部路由
resolve()
})
}
}

1.3.3 配置验证权限的方法

找到 ​​\src\utils\permission.js​​ 配置验证是否具有权限的验证方法

import store from '@/store'
// 检查是否有权限
export function hasPermission(roles, route) {
if (roles.menus && route.name) {
return roles.menus.some(role => {
return route.name.toLowerCase() === role.toLowerCase()
})
} else {
return false
}
}
// 检查是否有权限点
export function hasPermissionPoint(point) {
let points = store.getters.roles.points
if (points) {
return points.some(it => it.toLowerCase() === point.toLowerCase())
} else {
return false
}
}

1.3.4 修改登录和获取信息的请求接口

(1)关闭模拟测试接口

​\mock\index.js​​ 中不加载登录(login)以及(profile)的模拟测试

import Mock from 'mockjs'
import TableAPI from './table'
import ProfileAPI from './profile'
import LoginAPI from './login'
Mock.setup({
//timeout: '1000'
})
Mock.mock(/\/table\/list\.*/, 'get', TableAPI.list)
//Mock.mock(/\/frame\/profile/, 'post', ProfileAPI.profile)
//Mock.mock(/\/frame\/login/, 'post', LoginAPI.login)

1.4 权限测试

(1) 菜单测试

分配好权限之后,重新登录前端页面,左侧菜单已经发生了变化。

(2)对需要进行权限控制的(权限点)验证测试

页面添加校验方法

methods: {
checkPoint(point){
return hasPermissionPoint(point);
}
}

使用v-if验证权限是否存在,其中参数为配置的权限点标识

<el-button  type="primary" v-if="checkPoint('POINT-USER-ADD')" size="mini" icon="elicon-plus" @click="handlAdd">新增员工</el-button>