微服务迁移记(五):WEB层搭建(4)-简单的权限管理
一、redis搭建
二、WEB层主要依赖包
三、FeignClient通用接口
以上三项,参考《微服务迁移记(五):WEB层搭建(1)》
四、SpringSecurity集成
参考:《微服务迁移记(五):WEB层搭建(2)-SpringSecurity集成》
五、FreeMarker集成
参考:《微服务迁移记(五):WEB层搭建(3)-FreeMarker集成》
六、简单权限管理
实现一个简单的到按钮级权限管理,基于数据库扩展。不支持数据级权限,菜单只到二级(可以扩展至无限级,一般MIS系统,三级应该够了)
1. 数据库设计
用户表:tb_user
角色表:tb_role
用户角色:tb_user_role,外键:user_id,role_id
菜单表:tb_tree
菜单功能按钮表:tb_tree_func,外键:tree_id
角色菜单权限:tb_role_right(应该叫tb_role_tree更合适点),外键:role_id,tree_id
角色菜单下功能按钮:tb_roletree_func,外键:role_right_id,tree_id,role_tree_func(这个键没有拉外键过来),三个外键组成联合主键。
外键关系见下图
2. 用户登录后,左侧二级菜单展示
数据源:获取所有菜单及按钮的权限,前文提到的SpringSecurity里,MyUserDetail里我加过一个roletree的字段,会一起扔到session里(用户更换权限后,需要退出重新登录才能生效)
//获取所有树及按钮,含当前角色是否有权限 public Map<String,Object> getAllTreeWithRoleRight(int role_id){ String sql = "SELECT a.tree_id,a.tree_name,a.sup_tree_id,a.taxis,IFNULL(b.tree_id,0) as perm FROM " + " tb_tree a left JOIN " + "(SELECT rr.tree_id FROM tb_role_right rr " + "WHERE rr.role_id=?) b " + "ON b.tree_id=a.tree_id " + " WHERE a.del_flag=0 AND enable_flag=1 " + " ORDER BY sup_tree_id ASC,taxis ASC;"; List<Map<String,Object>> tree = this.jdbcTemplate.queryForList(sql,role_id); //再查询所有按钮 sql = "SELECT a.tree_id,b.tree_func_name,b.tree_func_namedesc,IFNULL(c.tree_id,0) as perm FROM tb_tree a INNER JOIN tb_tree_func b ON b.tree_id=a.tree_id " + "left JOIN " + "(SELECT rt.tree_id,rt.role_id,rt.tree_func_name FROM tb_role_treefunc rt WHERE rt.role_id=?) c " + "ON c.tree_id=a.tree_id AND c.tree_func_name=b.tree_func_name"; List<Map<String,Object>> func = jdbcTemplate.queryForList(sql,role_id); Map<String,Object> map = new HashMap<String,Object>(); map.put("tree",tree); map.put("func",func); return map; }
freemarker前台展示二级菜单:
<div id="sidebar-collapse" class="col-sm-3 col-lg-2 sidebar"> <ul class="nav menu"> <li class="active"><a href="javascript:goto('0000','/home');"><span class="glyphicon glyphicon-home"></span> 首页</a></li> <#list treelist as tree> <#if tree.sup_tree_id == 0> <li class="parent "> <a data-toggle="collapse" href="#sub-item-${tree.tree_id?c}"> <span class="glyphicon ${tree.tree_icon}"></span> ${tree.tree_name} </a> <ul class="children collapse" id="sub-item-${tree.tree_id?c}"> <#list treelist as tree_c> <#if tree_c.sup_tree_id==tree.tree_id> <li><a href="javascript:goto(${tree_c.tree_id?c},'${tree_c.link_addr!}')"><span class="glyphicon ${tree_c.tree_icon}"></span> ${tree_c.tree_name}</a></li> </#if> </#list> </ul> </li> </#if> </#list> <li role="presentation" class="divider"></li> <li><a href="/manage/Login/logout"><span class="glyphicon glyphicon-user"></span> 注销</a></li> </ul> </div><!--/.sidebar-->
最终展示效果如下:
2. 模块中按钮权限控制
通过自定义FreeMarker标签,实现前台按钮展示或隐藏
PermissionTagDirective 继承自TemplateDirectiveModel ,引用时需要传递tree_id和permissiontype两个字段进来。
package com.zyproject.web.freemarker; import com.zyproject.entity.RoleTreefuncEntity; import com.zyproject.entity.UserEntity; import com.zyproject.web.secrity.MyUserDetails; import freemarker.core.Environment; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.List; import java.util.Map; /** * @program: zyproject * @description: freemarker自定义标签,实现按钮级权限控制 * @author: zhouyu(zhouyu629 # qq.com) * @create: 2020-02-18 **/ @Component public class PermissionTagDirective implements TemplateDirectiveModel { private static String permissionType = "ptype"; //A新增,D删除等,与数据库对应 private static String Tree = "tree_id"; //菜单id @Override public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException { String ptype = map.get(permissionType).toString(); String tree_id = map.get(Tree).toString(); //当前登录用户 MyUserDetails myUserDetails = (MyUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); //判断权限,当前tree下,是否有ptype权限 if(this.checkRight(myUserDetails.getTreefuncEntities(),ptype,tree_id)) { templateDirectiveBody.render(environment.getOut()); } } private boolean checkRight(List<RoleTreefuncEntity> roleTreefuncEntities,String ptype,String tree_id){ for (RoleTreefuncEntity roleTreefuncEntity :roleTreefuncEntities) { if(roleTreefuncEntity.getTree_id() == Integer.valueOf(tree_id) && roleTreefuncEntity.getTree_func_name().equals(ptype)){ return true; } } return false; } }
然后把这个标签注册到FreeMarker
package com.zyproject.web.freemarker; import freemarker.template.Configuration; import freemarker.template.TemplateException; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.IOException; /** * @program: zyproject * @description: 注册标签到Freemarker * @author: zhouyu(zhouyu629 # qq.com) * @create: 2020-02-18 **/ @Component public class CustomeFreemakerConfigure implements ApplicationContextAware { @Autowired Configuration configuration; @Autowired private PermissionTagDirective permissionTagDirective; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { } @PostConstruct public void setSharedVariable() throws IOException, TemplateException{ configuration.setSharedVariable("perm",permissionTagDirective); } }
前台引用标签,标签里的参数tree_id,是在BaseController里扔到前台的,所有Controller都继承自BaseController(这部分代码略)
<@perm tree_id="${tree_id}" ptype="M"> <button name="btnModify" onclick="edit(${roles.role_id})" type="button" class="btn btn-primary btn-sm"><span class="glyphicon glyphicon-th" aria-hidden="true"></span>修改</button> </@perm> <@perm tree_id="${tree_id}" ptype="D"> <button name="btnDel" type="button" onclick="del(${roles.role_id})" class="btn btn-danger btn-sm"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除</button> </@perm> <@perm tree_id="${tree_id}" ptype="R"> <button name="btnRight" type="button" onclick="setRight(${roles.role_id})" class="btn btn-success btn-sm"><span class="glyphicon glyphicon-tags" aria-hidden="true"></span>权限</button> </@perm>
最终展示效果如下,后面三个按钮根据角色权限分配得来:
待完善:
1. BaseController里还要判断下进入每个模块是否有相应的权限,防止用户通过直接输入url地址绕过自定义标签的判断
2. 目前权限只到菜单和按钮级别,如果这个功能模块有多个子界面,权限都跟主界面保持一致