权限治理中,角色受权与认证属于权限模块中的要害模块,角色受权即是将角色可能操作的菜单资源分配给指定角色的行为,角色认证即是当用户表演指定角色登录零碎后零碎对于用户操作的资源进行权限校验的操作,意思这里说明确了,那么在代码中应该具体怎么实现呢?
角色受权与认证的形式
- 前端页面展现管制
- 后端权限访问控制
最全的学习材料 → 卫星:lezijie007(程序员暗号:1024)
案例实操
角色受权
树形数据展现
实现角色记录根本 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();
}
资源数据 ztree 显示
- role.js 增加受权点击事件
// 头工具栏事件
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
});
}
- RoleController.java 增加视图转发办法
@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>
- 增加 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);
}
})
}
角色受权
权限记录增加后端实现
- RoleService.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);
}
}
- RoleController.java
@RequestMapping("addGrant")
@ResponseBody
public ResultInfo addGrant(Integer[] mids,Integer roleId){roleService.addGrant(mids,roleId);
return success("权限增加胜利");
}
权限记录增加前端外围 js
批改 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 复选框是否抉择属性配置参考这里。
资源查问后端办法实现
- ModuleService.java
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;
}
- 角色领有权限 sql 查问
<select id="queryRoleHasAllModuleIdsByRoleId" parameterType="int" resultType="java.lang.Integer">
select module_id from t_permission where role_id=#{roleId}
</select>
- ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
public List<TreeDto> queryAllModules(Integer roleId){return moduleService.queryAllModules02(roleId);
}
权限回显前端 js
这里批改 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 指令 + 内建函数实现,指令与内建函数操作参考这里。
登录用户角色领有权限查问实现
- IndexController.java
/**
* 后端治理主页面
* @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";
}
- PermissionService.java
@Service
public class PermissionService extends BaseService<Permission,Integer> {
@Autowired
private PermissionMapper permissionMapper;
public List<String> queryUserHasRolesHasPermissions(Integer userId) {return permissionMapper.queryUserHasRolesHasPermissions(userId);
}
}
- PermissionMapper.java & PermissionMapper.xml
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+ 自定义注解实现
自定义注解 @RequirePermission
@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);
}
定义 aop 切面类拦挡指定注解标注的办法
@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"); // 应用类加载器加载类
//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());
}
}
}
}
}