权限管理中,角色授权与认证属于权限模块中的关键模块,角色授权即是将角色能够操作的菜单资源分配给指定角色的行为,角色认证即是当用户扮演指定角色登录系统后系统对于用户操作的资源进行权限校验的操作,意思这里说明白了,那么在代码中应该具体怎么实现呢?
完成角色记录基本 crud 功能之后,接下来实现角色授权功能,这里实现角色授权首先完成待授权资源显示功能。对于资源的显示,这里使用开源的 tree 插件 ztree。
前端 ztree 显示的资源数据格式参考这里。
ModuleMapper.xml <select id="queryAllModules" resultType="com.xxxx.crm.dto.TreeDto"> select id, IFNULL(parent_id,0) as pId, module_name AS name from t_module where is_valid=1 </select> ModuleService.java public List<TreeDto> queryAllModules(){ return moduleMapper.queryAllModules(); } ModuleController.java @RequestMapping("queryAllModules") @ResponseBody public List<TreeDto> queryAllModules(){ return moduleService.queryAllModules(); }views/role 目录下添加 grant.ftl 模板文件
<html> <head> <link rel="stylesheet" href="${ctx}/static/js/zTree_v3-3.5.32/css/zTreeStyle/zTreeStyle.css" type="text/css"> <script type="text/javascript" src="${ctx}/static/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script> <script type="text/javascript" src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.core.js"></script> <script type="text/javascript" src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.excheck.js"></script> </head> <body> <div id="test1" class="ztree"></div> <input id="roleId" value="${roleId!}" type="hidden"> <script type="text/javascript"> var ctx="${ctx}"; </script> <script type="text/javascript" src="${ctx}/static/js/role/grant.js"></script> </body> </html> 添加 grant.js var zTreeObj; $(function () { loadModuleInfo(); }); function loadModuleInfo() { $.ajax({ type:"post", url:ctx+"/module/queryAllModules" dataType:"json", success:function (data) { // zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解) var setting = { data: { simpleData: { enable: true } }, view:{ showLine: false // showIcon: false }, check: { enable: true, chkboxType: { "Y": "ps", "N": "ps" } } }; var zNodes =data; zTreeObj=$.fn.zTree.init($("#test1"), setting, zNodes); } }) }修改 grant.js 文件 添加 ztree 复选框点击回调 onCheck 事件。
var zTreeObj; $(function () { loadModuleInfo(); }); function loadModuleInfo() { $.ajax({ type:"post", url:ctx+"/module/queryAllModules", dataType:"json", success:function (data) { // zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解) var setting = { data: { simpleData: { enable: true } }, view:{ showLine: false // showIcon: false }, check: { enable: true, chkboxType: { "Y": "ps", "N": "ps" } }, callback: { onCheck: zTreeOnCheck } }; var zNodes =data; zTreeObj=$.fn.zTree.init($("#test1"), setting, zNodes); } }) } function zTreeOnCheck(event, treeId, treeNode) { var nodes= zTreeObj.getCheckedNodes(true); var roleId=$("#roleId").val(); var mids="mids="; for(var i=0;i<nodes.length;i++){ if(i<nodes.length-1){ mids=mids+nodes[i].id+"&mids="; }else{ mids=mids+nodes[i].id; } } $.ajax({ type:"post", url:ctx+"/role/addGrant", data:mids+"&roleId="+roleId, dataType:"json", success:function (data) { console.log(data); } }) }这里要实现已添加的角色记录权限再次查看或授权时显示原始权限的功能,ztree 复选框是否选择属性配置参考这里。
这里修改 grant.js 查询资源时传入当前选择角色 id
function loadModuleInfo() { $.ajax({ type:"post", url:ctx+"/module/queryAllModules", data:{ roleId:$("#roleId").val() }, dataType:"json", success:function (data) { // zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解) var setting = { data: { simpleData: { enable: true } }, view:{ showLine: false // showIcon: false }, check: { enable: true, chkboxType: { "Y": "ps", "N": "ps" } }, callback: { onCheck: zTreeOnCheck } }; var zNodes =data; zTreeObj=$.fn.zTree.init($("#test1"), setting, zNodes); } }) }当完成角色权限添加功能后,下一步就是对角色操作的资源进行认证操作,这里对于认证包含两块:
菜单级别显示控制后端方法访问控制系统根据登录用户扮演的不同角色来对登录用户操作的菜单进行动态控制显示操作,这里显示的控制使用 freemarker 指令+内建函数实现,指令与内建函数操作参考这里。
这里仅显示部分菜单控制。
<#if permissions?seq_contains("60")> <li class="layui-nav-item"> <a href="javascript:;" class="layui-menu-tips"><i class="fa fa-gears"></i><span class="layui-left-nav"> 系统设置</span> <span class="layui-nav-more"></span></a> <dl class="layui-nav-child"> <#if permissions?seq_contains("6010")> <dd> <a href="javascript:;" class="layui-menu-tips" data-type="tabAdd" data-tab-mpi="m-p-i-11" data-tab="user/index" target="_self"><i class="fa fa-user"></i><span class="layui-left-nav"> 用户管理</span></a> </dd> </#if> <#if permissions?seq_contains("6020")> <dd class=""> <a href="javascript:;" class="layui-menu-tips" data-type="tabAdd" data-tab-mpi="m-p-i-12" data-tab="role/index" target="_self"><i class="fa fa-tachometer"></i><span class="layui-left-nav"> 角色管理</span></a> </dd> </#if> <#if permissions?seq_contains("6030")> <dd class=""> <a href="javascript:;" class="layui-menu-tips" data-type="tabAdd" data-tab-mpi="m-p-i-13" data-tab="module/index" target="_self"><i class="fa fa-tachometer"></i><span class="layui-left-nav"> 菜单管理</span></a> </dd> </#if> </dl> </li> </#if>实现了菜单级别显示控制,但最终客户端有可能会通过浏览器来输入资源地址从而越过 ui 界面来访问后端资源,所以接下来加入控制方法级别资源的访问控制操作,这里使用 aop+自定义注解实现
从 JDK5 开始,Java 增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。下面我们来看看如何自定义注解。
创建自定义注解类
package com.lebyte.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /* @Target,@Retention,@Inherited,@Documented * 这四个是对注解进行注解的元注解,负责自定义的注解的属性 */ @Target({ElementType.TYPE,ElementType.METHOD}) //表示注解的作用对象,ElementType.TYPE表示类,ElementType.METHOD表示方法 @Retention(RetentionPolicy.RUNTIME) //表示注解的保留机制,RetentionPolicy.RUNTIME表示运行时注解 @Inherited //表示该注解可继承 @Documented //表示该注解可生成文档 public @interface Design { String author(); //注解成员,如果注解只有一个成员,则成员名必须为value(),成员类型只能为原始类型 int data() default 0; //注解成员,默认值为0 }使用注解
package com.lebyte; import com.lebyte.annotations.Design; @Design(author="lebyte",data=100) //使用自定义注解,有默认值的成员可以不用赋值,其余成员都要赋值 public class Person { @Design(author="lebyte",data=90) public void live(){ } }解析注解
package com.lebyte; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import com.lebyte.annotations.Design; public class Main { public static void main(String[] args) throws ClassNotFoundException { Class c=Class.forName("com.lebyte.Person"); //使用类加载器加载类 //1、找到类上的注解 if(c.isAnnotationPresent(Design.class)){ //判断类是否被指定注解注解 Design d=(Design) c.getAnnotation(Design.class); //拿到类上的指定注解实例 System.out.println(d.data()); } //2、找到方法上的注解 Method[] ms=c.getMethods(); for(Method m:ms){ if(m.isAnnotationPresent(Design.class)){ //判断方法是否被指定注解注解 Design d=m.getAnnotation(Design.class); //拿到类上的指定注解实例 System.out.println(d.data()); } } //3、另外一种方法 for(Method m:ms){ Annotation[] as=m.getAnnotations(); //拿到类上的注解集合 for(Annotation a:as){ if(a instanceof Design){ //判断指定注解 Design d=(Design) a; System.out.println(d.data()); } } } } }