权限管理与jwt鉴权
学习目标:
理解权限管理的需求以及设计思路实现角色分配和权限分配
理解常见的认证机制
能够使用JWT完成微服务Token签发与验证
权限管理
需求分析
完成权限(菜单,按钮(权限点),API接口)的基本操作
权限与菜单,菜单与按钮,菜单与API接口都是一对一关系。为了方便操作,在SAAS-HRM系统的表设计中,采用基于共享主键的形式实现一对一关系维护,并且数据库约束,一切的关系维护需要程序员在代码中实现。
后端实现
实体类
在系统微服务中创建权限,菜单,按钮(权限点),API对象的实体类
权限实体类Permission
package com.ihrm.domain.system; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; @Entity @Table(name = "pe_permission") @Getter @Setter @NoArgsConstructor @DynamicInsert(true) @DynamicUpdate(true) public class Permission implements Serializable { private static final long serialVersionUID = -4990810027542971546L; /** * 主键 */ @Id private String id; /** * 权限名称 */ private String name; /** * 权限类型 1为菜单 2为功能 3为API */ private Integer type; /** * 权限编码 */ private String code; /** * 权限描述 */ private String description; //父权限 private String pid; //是否查询全部权限 0 :查询全部,1 :只查询企业所属权限 private Integer enVisible; public Permission(String name, Integer type, String code, String description) { this.name = name; this.type = type; this.code = code; this.description = description; } }
骚戴理解:pid是指的父权限,就是我菜单权限下面可能有一套按钮或者API,那么这些就是子权限,那么它们的父权限就是这个菜单的id。enVisible表示是否查询全部权限(0 :查询全部,1 :只查询企业所属权限)也就是0表示更加高级的权限,连企业都访问不了的,只有超级管理员可以看到的权限
权限菜单(权限点)实体类PermissionPoint
@Entity @Table(name = "pe_permission_menu") @Getter @Setter public class PermissionMenu implements Serializable { private static final long serialVersionUID = -1002411490113957485L; @Id private String id; //主键 private String menuIcon; //展示图标 private String menuOrder; //排序号 }
- 权限菜单(权限点)实体类 PermissionPoint
@Entity @Table(name = "pe_permission_point") @Getter @Setter public class PermissionPoint implements Serializable { private static final long serialVersionUID = -1002411490113957485L; @Id private String id; private String pointClass; private String pointIcon; private String pointStatus; }
- 权限API实体类 PermissionApi
@Entity @Table(name = "pe_permission_api") @Getter @Setter public class PermissionApi implements Serializable { private static final long serialVersionUID = -1803315043290784820L; @Id private String id; private String apiUrl; private String apiMethod; private String apiLevel;//权限等级,1为通用接口权限,2为需校验接口权限 }
持久层
- 权限持久化类
/** * 权限数据访问接口 */ public interface PermissionDao extends JpaRepository<Permission, String>, JpaSpecificationExecutor<Permission> { List<Permission> findByTypeAndPid(int type,String pid); }
- 权限菜单持久化类
public interface PermissionMenuDao extends JpaRepository<PermissionMenu, String>, JpaSpecificationExecutor<PermissionMenu> { }1. pub
权限按钮(点)持久化类
public interface PermissionPointDao extends JpaRepository<PermissionPoint, String>, JpaSpecificationExecutor<PermissionPoint> { }
- 权限API持久化类
public interface PermissionApiDao extends JpaRepository<PermissionApi, String>, JpaSpecificationExecutor<PermissionApi> { }
业务逻辑
1. papackage com.ihrm.system.service; import com.ihrm.common.entity.ResultCode; import com.ihrm.common.exception.CommonException; import com.ihrm.common.utils.BeanMapUtils; import com.ihrm.common.utils.IdWorker; import com.ihrm.common.utils.PermissionConstants; import com.ihrm.domain.system.*; import com.ihrm.system.dao.*; import com.ihrm.system.dao.PermissionDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.List; import java.util.Map; @Service @Transactional public class PermissionService { @Autowired private PermissionDao permissionDao; @Autowired private PermissionMenuDao permissionMenuDao; @Autowired private PermissionPointDao permissionPointDao; @Autowired private PermissionApiDao permissionApiDao; @Autowired private IdWorker idWorker; /** * 1.保存权限 */ public void save(Map<String,Object> map) throws Exception { //设置主键的值 String id = idWorker.nextId()+""; //1.通过map构造permission对象 Permission perm = BeanMapUtils.mapToBean(map,Permission.class); perm.setId(id); //2.根据类型构造不同的资源对象(菜单,按钮,api) int type = perm.getType(); switch (type) { case PermissionConstants.PERMISSION_MENU: PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class); menu.setId(id); permissionMenuDao.save(menu); break; case PermissionConstants.PERMISSION_POINT: PermissionPoint point = BeanMapUtils.mapToBean(map,PermissionPoint.class); point.setId(id); permissionPointDao.save(point); break; case PermissionConstants.PERMISSION_API: PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class); api.setId(id); permissionApiDao.save(api); break; default: throw new CommonException(ResultCode.FAIL); } //3.保存 permissionDao.save(perm); } /** * 2.更新权限 */ public void update(Map<String,Object> map) throws Exception { Permission perm = BeanMapUtils.mapToBean(map,Permission.class); //1.通过传递的权限id查询权限 Permission permission = permissionDao.findById(perm.getId()).get(); permission.setName(perm.getName()); permission.setCode(perm.getCode()); permission.setDescription(perm.getDescription()); permission.setEnVisible(perm.getEnVisible()); //2.根据类型构造不同的资源 int type = perm.getType(); switch (type) { case PermissionConstants.PERMISSION_MENU: PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class); menu.setId(perm.getId()); permissionMenuDao.save(menu); break; case PermissionConstants.PERMISSION_POINT: PermissionPoint point = BeanMapUtils.mapToBean(map,PermissionPoint.class); point.setId(perm.getId()); permissionPointDao.save(point); break; case PermissionConstants.PERMISSION_API: PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class); api.setId(perm.getId()); permissionApiDao.save(api); break; default: throw new CommonException(ResultCode.FAIL); } //3.保存 permissionDao.save(permission); } /** * 3.根据id查询 * //1.查询权限 * //2.根据权限的类型查询资源 * //3.构造map集合 */ public Map<String, Object> findById(String id) throws Exception { Permission perm = permissionDao.findById(id).get(); int type = perm.getType(); Object object = null; if(type == PermissionConstants.PERMISSION_MENU) { object = permissionMenuDao.findById(id).get(); }else if (type == PermissionConstants.PERMISSION_POINT) { object = permissionPointDao.findById(id).get(); }else if (type == PermissionConstants.PERMISSION_API) { object = permissionApiDao.findById(id).get(); }else { throw new CommonException(ResultCode.FAIL); } Map<String, Object> map = BeanMapUtils.beanToMap(object); map.put("name",perm.getName()); map.put("type",perm.getType()); map.put("code",perm.getCode()); map.put("description",perm.getDescription()); map.put("pid",perm.getPid()); map.put("enVisible",perm.getEnVisible()); return map; } /** * 4.查询全部 * type : 查询全部权限列表type:0:菜单 + 按钮(权限点) 1:菜单2:按钮(权限点)3:API接口 * enVisible : 0:查询所有saas平台的最高权限,1:查询企业的权限 * pid :父id */ public List<Permission> findAll(Map<String, Object> map) { //1.需要查询条件 Specification<Permission> spec = new Specification<Permission>() { /** * 动态拼接查询条件 * @return */ public Predicate toPredicate(Root<Permission> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Predicate> list = new ArrayList<>(); //根据父id查询 if(!StringUtils.isEmpty(map.get("pid"))) { list.add(criteriaBuilder.equal(root.get("pid").as(String.class),(String)map.get("pid"))); } //根据enVisible查询 if(!StringUtils.isEmpty(map.get("enVisible"))) { list.add(criteriaBuilder.equal(root.get("enVisible").as(String.class),(String)map.get("enVisible"))); } //根据类型 type if(!StringUtils.isEmpty(map.get("type"))) { String ty = (String) map.get("type"); CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get("type")); if("0".equals(ty)) { in.value(1).value(2); }else{ in.value(Integer.parseInt(ty)); } list.add(in); } return criteriaBuilder.and(list.toArray(new Predicate[list.size()])); } }; return permissionDao.findAll(spec); } /** * 5.根据id删除 * //1.删除权限 * //2.删除权限对应的资源 * */ public void deleteById(String id) throws Exception { //1.通过传递的权限id查询权限 Permission permission = permissionDao.findById(id).get(); permissionDao.delete(permission); //2.根据类型构造不同的资源 int type = permission.getType(); switch (type) { case PermissionConstants.PERMISSION_MENU: permissionMenuDao.deleteById(id); break; case PermissionConstants.PERMISSION_POINT: permissionPointDao.deleteById(id); break; case PermissionConstants.PERMISSION_API: permissionApiDao.deleteById(id); break; default: throw new CommonException(ResultCode.FAIL); } } }
骚戴理解:需要注意的是菜单、按钮、api这三个子权限的id要和父权限id保持一致,也就是PermissionApi、PermissionMenu、PermissionPoint这三个类的id和Permission类保持一致!
把下面这段Java代码翻译成SQL更好理解
if(!StringUtils.isEmpty(map.get("type"))) { String ty = (String) map.get("type"); CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get("type")); //查询全部权限列表type:0:菜单 + 按钮(权限点) 1:菜单2:按钮(权限点)3:API接口 if("0".equals(ty)) { in.value(1).value(2); }else{ in.value(Integer.parseInt(ty)); } list.add(in); }
这段Java代码是用于构建JPA动态查询的条件语句。它的作用是根据map中的"type"键值来判断是否需要加入一个子条件。
代码逻辑如下:
检查map中是否存在"type"键,并且其值不为空;
如果符合条件,则获取"type"的值ty,并且使用CriteriaBuilder构造一个In条件对象in;
如果ty为"0",则添加in条件的取值为1和2;否则将ty转换为int类型并添加到in条件中;
最后将这个in条件添加到条件集合list中。
以下是该Java代码对应的SQL语句:
SELECT*FROM 表名 WHERE type IN (1, 2) -- 当map.get("type")中的值为"0"时 SELECT*FROM 表名 WHERE type = ? -- 当map.get("type")中的值为除"0"以外的值时
这段Java代码会在生成的SQL语句中自动完成参数的绑定,具体SQL语句中的占位符?由框架来替换成具体的值。
控制器
package com.ihrm.system.controller; import com.ihrm.common.entity.PageResult; import com.ihrm.common.entity.Result; import com.ihrm.common.entity.ResultCode; import com.ihrm.domain.system.Permission; import com.ihrm.domain.system.User; import com.ihrm.system.service.PermissionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; //1.解决跨域 @CrossOrigin //2.声明restContoller @RestController //3.设置父路径 @RequestMapping(value="/sys") public class PermissionController { @Autowired private PermissionService permissionService; /** * 保存 */ @RequestMapping(value = "/permission", method = RequestMethod.POST) public Result save(@RequestBody Map<String,Object> map) throws Exception { permissionService.save(map); return new Result(ResultCode.SUCCESS); } /** * 修改 */ @RequestMapping(value = "/permission/{id}", method = RequestMethod.PUT) public Result update(@PathVariable(value = "id") String id, @RequestBody Map<String,Object> map) throws Exception { //构造id map.put("id",id); permissionService.update(map); return new Result(ResultCode.SUCCESS); } /** * 查询列表 */ @RequestMapping(value = "/permission", method = RequestMethod.GET) public Result findAll(@RequestParam Map map) { List<Permission> list = permissionService.findAll(map); return new Result(ResultCode.SUCCESS,list); } /** * 根据ID查询 */ @RequestMapping(value = "/permission/{id}", method = RequestMethod.GET) public Result findById(@PathVariable(value = "id") String id) throws Exception { Map map = permissionService.findById(id); return new Result(ResultCode.SUCCESS,map); } /** * 根据id删除 */ @RequestMapping(value = "/permission/{id}", method = RequestMethod.DELETE) public Result delete(@PathVariable(value = "id") String id) throws Exception { permissionService.deleteById(id); return new Result(ResultCode.SUCCESS); } }
骚戴理解:这里我一开始搞错了,我以为下面的这个前端的Permission实体就是对应这后端的Permission对象,结果不是!导致我一直不能理解为什么后端要封装成一个map来接受参数而不是直接用Permission对象接收,这个前端的Permission实体其实是后端的Permission、PermissionApi、PermissionMenu、PermissionPoint的合体集合
所以这个前端的Permission实体可以用map来接收,也就是通过这么一个前端的Permission实体实现菜单、组件、API的管理,根据前端传过来的type来判断到底要操作什么,这也就是为什么就只有PermissionController这一个控制器却可以实现菜单、组件、API的增删改查操作
前端实现
引入权限管理模块
将资料module-permissions引入到工程的/src文件夹下,在/src/main.js完成模块注册
import permissions from '@/module-permissions/' // 权限管理 Vue.use(permissions, store)
配置API
在/src/api/base/目录下创建permissions.js
import {createAPI} from '@/utils/request' const api = "/sys/permission" export const list = data => createAPI(`${api}`, 'get', data) export const add = data => createAPI(`${api}`, 'post', data) export const update = data => createAPI(`${api}/${data.id}`, 'put', data) export const remove = data => createAPI(`${api}/${data.id}`, 'delete', data) export const detail = data => createAPI(`${api}/${data.id}`, 'get', data) export const saveOrUpdate = data => {return data.id?update(data):add(data)}
实现权限页面
<template> <div class="dashboard-container"> <div class="app-container"> <el-card shadow="never"> <el-button class="filter-item fr" size="small" style="margin-left: 10px;" @click="handleCreate(null,1);setPid(1,'0')" type="primary" icon="el-icon-edit">添加菜单</el-button> <el-table :data="dataList" fit style="width: 100%;" highlight-current-row> <el-table-column fixed prop="name" label="菜单名称" width="200px"> <template slot-scope="scope"> <i :class="scope.row.type==1?'ivu-icon fa fa-folder-open-o fa-fw':'ivu-icon el-icon-view'" :style="scope.row.type==1?'margin-left: 0px':'margin-left: 20px'"></i> <span @click="show(scope.$index,scope.row.id)">{{scope.row.name}}</span> </template> </el-table-column> <el-table-column fixed prop="code" label="权限标识" width="200"></el-table-column> <el-table-column fixed prop="description" label="描述" width="200"></el-table-column> <el-table-column fixed="right" label="操作"> <template slot-scope="scope"> <el-button v-if="scope.row.type==1" @click="handleCreate(null,2);setPid(2,scope.row.id)" type="text" size="small">添加权限点</el-button> <el-button @click="handlerApiList(scope.row.id)" type="text" size="small">查看api权限</el-button> <el-button @click="handleCreate(scope.row.id,scope.row.type);setPid(scope.row.type,scope.row.pid)" type="text" size="small">查看</el-button> <el-button @click="handleDelete(scope.row.id)" type="text" size="small">删除</el-button> </template> </el-table-column> </el-table> </el-card> </div> <el-dialog title="编辑权限" :visible.sync="dialogFormVisible" style="hight:100px;line-height:1px"> <el-form :model="formData" label-width="90px" style="margin-top:20px"> <el-form-item label="权限名称"> <el-input v-model="formData.name" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="权限标识"> <el-input v-model="formData.code" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="权限描述"> <el-input v-model="formData.description" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="企业可见"> <el-switch v-model="formData.enVisible" active-value="1" inactive-value="0" active-text="可见" inactive-text="不可见"> </el-switch> </el-form-item> <div v-if="type==1"> <el-form-item label="菜单顺序"> <el-input v-model="formData.menuOrder" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="菜单icon"> <el-input v-model="formData.menuIcon" autocomplete="off" style="width:90%"></el-input> </el-form-item> </div> <div v-else-if="type==2"> <el-form-item label="按钮样式"> <el-input v-model="formData.pointClass" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="按钮icon"> <el-input v-model="formData.pointIcon" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="按钮状态"> <el-input v-model="formData.pointStatus" autocomplete="off" style="width:90%"></el-input> </el-form-item> </div> <div v-else-if="type==3"> <el-form-item label="api请求地址"> <el-input v-model="formData.apiUrl" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="api请求方式"> <el-input v-model="formData.apiMethod" autocomplete="off" style="width:90%"></el-input> </el-form-item> <el-form-item label="api类型"> <el-input v-model="formData.apiLevel" autocomplete="off" style="width:90%"></el-input> </el-form-item> </div> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="saveOrUpdate">确 定</el-button> </div> </el-dialog> <el-dialog title="API权限列表" :visible.sync="apiDialogVisible" style="hight:400px;line-height:1px"> <el-button class="filter-item fr" size="small" style="margin-left: 10px;" @click="handleCreate(null,1);setPid(3,pid)" type="primary" icon="el-icon-edit">添加api权限</el-button> <el-table :data="apiList" fit style="width: 100%;" max-height="250" > <el-table-column fixed prop="name" label="菜单名称" width="120px"></el-table-column> <el-table-column fixed prop="code" label="权限标识" width="200"></el-table-column> <el-table-column fixed prop="description" label="描述" width="200"></el-table-column> <el-table-column fixed="right" label="操作" width="200"> <template slot-scope="scope"> <el-button @click="handleCreate(scope.row.id,scope.row.type);setPid(scope.row.type,scope.row.pid)" type="text" size="small">查看</el-button> <el-button @click="handleDelete(scope.row.id);handlerApiList(pid)" type="text" size="small">删除</el-button> </template> </el-table-column> </el-table> </el-dialog> </div> </template> <script> import {saveOrUpdate,list,detail,remove} from "@/api/base/permissions" export default { name: 'permissions-table-index', data() { return { MenuList: 'menuList', type:0, pid:"", dialogFormVisible:false, apiDialogVisible:false, formData:{}, dataList:[], apiList:[], pointEnable:{} } }, methods: { setPid(type,pid){ this.pid = pid; this.type = type }, handleCreate(id) { if(id && id !=undefined) { detail({id}).then(res => { this.formData = res.data.data this.dialogFormVisible=true }) }else{ this.formData = {} this.dialogFormVisible=true } }, saveOrUpdate() { this.formData.type = this.type this.formData.pid = this.pid saveOrUpdate(this.formData).then(res => { this.$message({message:res.data.message,type:res.data.success?"success":"error"}); if(res.data.success){ this.formData={}; this.dialogFormVisible=false; } if(this.type ==3){ this.handlerApiList(this.pid); }else{ this.getList(); this.pointEnable = {} } }) }, handleDelete(id) { remove({id}).then(res=> { this.$message({message:res.data.message,type:res.data.success?"success":"error"}); }) }, getList() { list({type:1,pid:0}).then(res=> { this.dataList = res.data.data }) }, show(index,id) { if(!this.pointEnable[id] == null || this.pointEnable[id]==undefined){ list({type:2,pid:id}).then(res=> { if(res.data.data.length <=0) { this.$message.error("无子权限") }else{ for(var i = 0 ; i <res.data.data.length;i++) { this.dataList.splice(index+1,0,res.data.data[i]); } this.pointEnable[id] = res.data.data.length; } }) }else{ this.dataList.splice(index+1,this.pointEnable[id]) this.pointEnable[id] = null; } }, handlerApiList(id) { this.pid = id; list({type:3,pid:id}).then(res=> { this.apiList = res.data.data this.apiDialogVisible = true; }) } }, created () { this.getList(); } } </script> <style rel="stylesheet/scss" lang="scss" scoped> .alert { margin: 10px 0px; } .pagination { margin-top: 10px; // text-align: right; } </style> <style> .el-table th { background-color: #fafafa; } .el-table th.is-leaf { border-bottom: 2px solid #e8e8e8; } .el-table__row i{ font-style:normal} </style>
分配角色
需求分析
由于使用了RBAC模型对权限进行统一管理,所以每个SAAS-HRM平台的用户都应该具有角色的信息。进而通过角 色完成对权限的识别。众所周知,一个用户可以具有很多的角色,一个角色可以被分配给不同的用户。所以用户和角色之间是多对多关系。
服务端代码实现
- 改造用户实体类,添加角色的id集合属性,表明一个用户具有的多个角色id
在com.ihrm.system.domain.User用户实体类中添加与角色的多对多关系并进行JPA的配置
@ManyToMany @JsonIgnore @JoinTable(name="pe_user_role",joinColumns= {@JoinColumn(name="user_id",referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")} ) private Set<Role> roles = new HashSet<Role>();//用户与角色 多对多
在com.ihrm.system.domain.Role角色实体类中配置角色与用户的多对多关系并进行JPA配置
@JsonIgnore @ManyToMany(mappedBy="roles") private Set<User> users = new HashSet<User>(0);//角色与用户 多对多
骚戴理解:通过在实体类里这样配置就可以实现多个表关联自动修改,也就是你只需要修改实体类他就会自动去维护修改数据库中的关联表和中间表
- 在com.ihrm.system.controller.UserController添加分配角色的控制器方法实现
/** * 分配角色 */ @RequestMapping(value = "/user/assignRoles", method = RequestMethod.PUT) public Result assignRoles(@RequestBody Map<String,Object> map) { //1.获取被分配的用户id String userId = (String) map.get("id"); //2.获取到角色的id列表 List<String> roleIds = (List<String>) map.get("roleIds"); //3.调用service完成角色分配 userService.assignRoles(userId,roleIds); return new Result(ResultCode.SUCCESS); }
- 业务逻辑层添加分配角色的业务方法
/** * 分配角色 */ public void assignRoles(String userId,List<String> roleIds) { //1.根据id查询用户 User user = userDao.findById(userId).get(); //2.设置用户的角色集合 Set<Role> roles = new HashSet<>(); for (String roleId : roleIds) { Role role = roleDao.findById(roleId).get(); roles.add(role); } //设置用户和角色集合的关系 user.setRoles(roles); //3.更新用户 userDao.save(user); }
前端代码实现
- \src\module-employees 添加分配角色的组件
<template> <div class="add-form"> <el-dialog title="分配角色" :visible.sync="roleFormVisible" style="height:500px"> <el-form :model="formBase" label-position="left" label-width="120px" style='margin-left:120px;'> <el-checkbox-group v-model="allRoles"> <el-checkbox v-for="(item,index) in roles" :label="item.id" :key="index">{{item.name}}</el-checkbox> </el-checkbox-group> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="createData">提交</el-button> <el-button @click="roleFormVisible=false">取消</el-button> </div> </el-dialog> </div> </template> <script> import {findAll} from "@/api/base/role" import {assignRoles,detail} from "@/api/base/users" export default { data () { return { roleFormVisible:false, formBase:{}, allRoles:[], data:[], roles:[], id:null } }, methods: { toAssignPrem(id) { detail({id:id}).then(res1 => { this.allRoles = res1.data.data.roleIds; findAll().then(res => { this.id = id; this.roles = res.data.data this.roleFormVisible=true }) }) }, createData() { assignRoles({id:this.id,roleIds:this.allRoles}).then(res => { console.log(this.allRoles) this.$message({message:res.data.message,type:res.data.success?"success":"error"}); this.roleFormVisible=false }) } } } </script>
- \src\module-employees\pages\employees-list.vue 引入组件
1. <!--分配角色组件 --> 2. <component v-bind:is="addRole" ref="addRole"></component>
分配权限
需求分析
完成对角色权限的分配。
服务端代码实现
- 角色实体类中添加与权限的多对多关系并进行JPA配置
@JsonIgnore //忽略json转化 @ManyToMany @JoinTable(name="pe_role_permission", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}, inverseJoinColumns= {@JoinColumn(name="permission_id",referencedColumnName="id")}) private Set<Permission> permissions = new HashSet<Permission>(0);//角色与模块 多对多
- 控制器类( com.ihrm.system.controller.RoleController )添加权限分配
/** * 分配权限 */ @RequestMapping(value = "/role/assignPrem", method = RequestMethod.PUT) public Result assignPrem(@RequestBody Map<String,Object> map) { //1.获取被分配的角色的id String roleId = (String) map.get("id"); //2.获取到权限的id列表 List<String> permIds = (List<String>) map.get("permIds"); //3.调用service完成权限分配 roleService.assignPerms(roleId,permIds); return new Result(ResultCode.SUCCESS); }
- 服务层中添加分配权限方法
/** * 分配权限 */ public void assignPerms(String roleId,List<String> permIds) { //1.获取分配的角色对象 Role role = roleDao.findById(roleId).get(); //2.构造角色的权限集合 Set<Permission> perms = new HashSet<>(); for (String permId : permIds) { Permission permission = permissionDao.findById(permId).get(); //需要根据父id和类型查询API权限列表 List<Permission> apiList = permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId()); perms.addAll(apiList);//自定赋予API权限 perms.add(permission);//当前菜单或按钮的权限 } System.out.println(perms.size()); //3.设置角色和权限的关系 role.setPermissions(perms); //4.更新角色 roleDao.save(role); }
骚戴理解:上面的分配权限的逻辑和分配角色的逻辑基本一样的,区别就在于下面这两行代码,如果这个权限是按钮,那么这个按钮一定是绑定了一个api权限的,所以如果前端赋按钮权限,那么api权限也要一起赋予,这里一开始我觉得按钮权限和api权限是一对一的关系,所以不能理解为什么用List来接收,后面发现有的时候一个按钮会发多个请求,例如“分配角色”按钮就会发两个请求
List<Permission> apiList = permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId()); perms.addAll(apiList);//自定赋予API权限
- 数据层
package com.ihrm.system.dao; import com.ihrm.domain.system.Permission; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import java.util.List; /** * 权限 */ public interface PermissionDao extends JpaRepository<Permission, String>, JpaSpecificationExecutor<Permission> { List<Permission> findByTypeAndPid(int type,String pid); }
骚戴理解:这里直接在PermissionDao里面写了findByTypeAndPid方法,但是没有写具体的sql,我当时就很纳闷他是怎么实现的,这其实就是根据这个方法的命名来实现的,findByTypeAndPid他会根据这个方法名解析出Type和Pid字段,然后用and拼接成sql语句,实现的效果就是按这两个值去查询
前端代码实现
- 在\src\module-settings\components\role-list.vue绑定权限按钮
<el-table-column fixed="right" label="操作" align="center" width="250"> <template slot-scope="scope"> <el-button @click="handlerPerm(scope.row)" type="text" size="small">分配权限</elbutton> <el-button @click="handleUpdate(scope.row)" type="text" size="small">修改</elbutton> <el-button @click="handleDelete(scope.row)" type="text" size="small">删除</elbutton> </template> </el-table-column>
在\\src\api\base\role.js中添加分配权限的API方法
export const assignPrem = data => createAPI(`/sys/role/assignPrem`, 'put', data)
\src\module-settings\components\role-list.vue使用Element-UI构造权限树
<el-dialog :title="'为【'+formData.name+'】分配权限'" :visible.sync="permFormVisible" style="hight:100px;line-height:1px"> <el-tree :data="treeData" default-expand-all show-checkbox node-key="id" ref="tree" :default-checked-keys="checkNodes" :props="{label:'name'}"> </el-tree> <div slot="footer" class="dialog-footer"> <el-button @click="permFormVisible = false">取 消</el-button> <el-button type="primary" @click="assignPrem">确 定</el-button> </div> </el-dialog>
- 完成添加权限
常见的认证机制
HTTP Basic Auth
HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth
Cookie Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie 对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏 览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效
OAuth
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资 源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频 编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样, OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证 权限管理的企业应用。