權限管理中,角色受權與認證屬於權限模塊中的關鍵模塊,角色受權便是將角色可以操做的菜單資源分配給指定角色的行爲,角色認證便是當用戶扮演指定角色登陸系統後系統對於用戶操做的資源進行權限校驗的操做,意思這裏說明白了,那麼在代碼中應該具體怎麼實現呢?javascript
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PWj63zYa-1599723384785)(https://imgkr.cn-bj.ufileos.c...]php
完成角色記錄基本 crud 功能以後,接下來實現角色受權功能,這裏實現角色受權首先完成待受權資源顯示功能。對於資源的顯示,這裏使用開源的 tree 插件 ztree。css
前端 ztree 顯示的資源數據格式參考這裏。html
<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>
public List<TreeDto> queryAllModules(){ return moduleMapper.queryAllModules(); }
@RequestMapping("queryAllModules") @ResponseBody public List<TreeDto> queryAllModules(){ return moduleService.queryAllModules(); }
//頭工具欄事件 table.on('toolbar(roles)', function(obj){ var checkStatus = table.checkStatus(obj.config.id); switch(obj.event){ case "add": openAddOrUpdateRoleDialog(); break; case "grant": openAddGrantDailog(checkStatus.data); break; }; }); function openAddGrantDailog(datas){ if(datas.length==0){ layer.msg("請選擇待受權角色記錄!", {icon: 5}); return; } if(datas.length>1){ layer.msg("暫不支持批量角色受權!", {icon: 5}); return; } var url = ctx+"/role/toAddGrantPage?roleId="+datas[0].id; var title="角色管理-角色受權"; layui.layer.open({ title : title, type : 2, area:["600px","280px"], maxmin:true, content : url }); }
@RequestMapping("toAddGrantPage") public String toAddGrantPage(Integer roleId,Model model){ model.addAttribute("roleId",roleId); return "role/grant"; }
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>
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); } }) }
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fUjjBzjZ-1599723384788)(https://imgkr.cn-bj.ufileos.c...]java
public void addGrant(Integer[] mids, Integer roleId) { /** * 核心表-t_permission t_role(校驗角色存在) * 若是角色存在原始權限 刪除角色原始權限 * 而後添加角色新的權限 批量添加權限記錄到t_permission */ Role temp =selectByPrimaryKey(roleId); AssertUtil.isTrue(null==roleId||null==temp,"待受權的角色不存在!"); int count = permissionMapper.countPermissionByRoleId(roleId); if(count>0){ AssertUtil.isTrue(permissionMapper.deletePermissionsByRoleId(roleId)<count,"權限分配失敗!"); } if(null !=mids && mids.length>0){ List<Permission> permissions=new ArrayList<Permission>(); for (Integer mid : mids) { Permission permission=new Permission(); permission.setCreateDate(new Date()); permission.setUpdateDate(new Date()); permission.setModuleId(mid); permission.setRoleId(roleId); permission.setAclValue(moduleMapper.selectByPrimaryKey(mid).getOptValue()); permissions.add(permission); } permissionMapper.insertBatch(permissions); } }
@RequestMapping("addGrant") @ResponseBody public ResultInfo addGrant(Integer[] mids,Integer roleId){ roleService.addGrant(mids,roleId); return success("權限添加成功"); }
修改 grant.js 文件 添加 ztree 複選框點擊回調 onCheck 事件。node
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 複選框是否選擇屬性配置參考這裏。jquery
public List<TreeDto> queryAllModules02(Integer roleId) { List<TreeDto> treeDtos=moduleMapper.queryAllModules(); // 根據角色id 查詢角色擁有的菜單id List<Integer> List<Integer> roleHasMids=permissionMapper.queryRoleHasAllModuleIdsByRoleId(roleId); if(null !=roleHasMids && roleHasMids.size()>0){ treeDtos.forEach(treeDto -> { if(roleHasMids.contains(treeDto.getId())){ // 說明當前角色 分配了該菜單 treeDto.setChecked(true); } }); } return treeDtos; }
<select id="queryRoleHasAllModuleIdsByRoleId" parameterType="int" resultType="java.lang.Integer"> select module_id from t_permission where role_id=#{roleId} </select>
@RequestMapping("queryAllModules") @ResponseBody public List<TreeDto> queryAllModules(Integer roleId){ return moduleService.queryAllModules02(roleId); }
這裏修改 grant.js 查詢資源時傳入當前選擇角色 idajax
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); } }) }
當完成角色權限添加功能後,下一步就是對角色操做的資源進行認證操做,這裏對於認證包含兩塊:sql
系統根據登陸用戶扮演的不一樣角色來對登陸用戶操做的菜單進行動態控制顯示操做,這裏顯示的控制使用 freemarker 指令+內建函數實現,指令與內建函數操做參考這裏。
/** * 後端管理主頁面 * @return */ @RequestMapping("main") public String main(HttpServletRequest request){ Integer userId = LoginUserUtil.releaseUserIdFromCookie(request); request.setAttribute("user",userService.selectByPrimaryKey(userId)); List<String> permissions=permissionService.queryUserHasRolesHasPermissions(userId); request.getSession().setAttribute("permissions",permissions); return "main"; }
@Service public class PermissionService extends BaseService<Permission,Integer> { @Autowired private PermissionMapper permissionMapper; public List<String> queryUserHasRolesHasPermissions(Integer userId) { return permissionMapper.queryUserHasRolesHasPermissions(userId); } }
public interface PermissionMapper extends BaseMapper<Permission,Integer> { List<String> queryUserHasRolesHasPermissions(Integer userId); }
<select id="queryUserHasRolesHasPermissions" parameterType="int" resultType="java.lang.String"> select distinct p.acl_value from t_user_role ur left join t_permission p on ur.role_id = p.role_id where ur.user_id=#{userId} </select>
這裏僅顯示部分菜單控制。
<#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+自定義註解實現
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequirePermission { String code() default ""; }
@RequestMapping("list") @ResponseBody @RequirePermission(code = "101001") public Map<String,Object> querySaleChancesByParams(Integer flag,HttpServletRequest request,SaleChanceQuery saleChanceQuery){ if(null !=flag &&flag==1){ // 查詢分配給當前登陸用戶 營銷記錄 saleChanceQuery.setAggsinMan(LoginUserUtil.releaseUserIdFromCookie(request)); } return saleChanceService.queryByParamsForTable(saleChanceQuery); }
@Component @Aspect public class PermissionProxy { @Autowired private HttpSession session; @Around(value = "@annotation(com.xxx.sys.annotaions.RequirePermission)") public Object around(ProceedingJoinPoint pjp) throws Throwable { List<String> permissions = (List<String>) session.getAttribute("permissions"); if(null == permissions || permissions.size()==0){ throw new NoPermissionException(); } Object result =null; MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); RequirePermission requirePermission = methodSignature.getMethod().getDeclaredAnnotation(RequirePermission.class); if(!(permissions.contains(requirePermission.code()))){ throw new NoPermissionException(); } result= pjp.proceed(); return result; } }
從 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"); //使用類加載器加載類 //一、找到類上的註解 if(c.isAnnotationPresent(Design.class)){ //判斷類是否被指定註解註解 Design d=(Design) c.getAnnotation(Design.class); //拿到類上的指定註解實例 System.out.println(d.data()); } //二、找到方法上的註解 Method[] ms=c.getMethods(); for(Method m:ms){ if(m.isAnnotationPresent(Design.class)){ //判斷方法是否被指定註解註解 Design d=m.getAnnotation(Design.class); //拿到類上的指定註解實例 System.out.println(d.data()); } } //三、另一種方法 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()); } } } } }
Method m:ms){
if(m.isAnnotationPresent(Design.class)){ //判斷方法是否被指定註解註解 Design d=m.getAnnotation(Design.class); //拿到類上的指定註解實例 System.out.println(d.data()); } } //三、另一種方法 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()); } } } }
}