权限管理中,角色授权与认证属于权限模块中的关键模块,角色授权即是将角色能够操作的菜单资源分配给指定角色的行为,角色认证即是当用户扮演指定角色登录系统后系统对于用户操作的资源进行权限校验的操作,意思这里说明白了,那么在代码中应该具体怎么实现呢?
角色授权与认证的方式
前端页面展示控制
后端权限访问控制
案例实操
角色授权
树形数据展示
完成角色记录基本 crud 功能之后,接下来实现角色授权功能,这里实现角色授权首先完成待授权资源显示功能。对于资源的显示,这里使用开源的 tree 插件 ztree。
资源数据查询后端实现
前端 ztree 显示的资源数据格式参考这里。
ModuleMapper.xml
<selectid="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
publicList<TreeDto>queryAllModules(){
returnmoduleMapper.queryAllModules();
}
ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
publicList<TreeDto>queryAllModules(){
returnmoduleService.queryAllModules();
}
资源数据 ztree 显示
role.js 添加授权点击事件
//头工具栏事件
table.on('toolbar(roles)',function(obj){
varcheckStatus=table.checkStatus(obj.config.id);
switch(obj.event){
case"add":
openAddOrUpdateRoleDialog();
break;
case"grant":
openAddGrantDailog(checkStatus.data);
break;
};
});
functionopenAddGrantDailog(datas){
if(datas.length==0){
layer.msg("请选择待授权角色记录!", {icon:5});
return;
}
if(datas.length>1){
layer.msg("暂不支持批量角色授权!", {icon:5});
return;
}
varurl=ctx+"/role/toAddGrantPage?roleId="+datas[0].id;
vartitle="角色管理-角色授权";
layui.layer.open({
title:title,
type:2,
area:["600px","280px"],
maxmin:true,
content:url
});
}
RoleController.java 添加视图转发方法
@RequestMapping("toAddGrantPage")
publicStringtoAddGrantPage(IntegerroleId,Modelmodel){
model.addAttribute("roleId",roleId);
return"role/grant";
}
准备显示资源数据模板
views/role 目录下添加 grant.ftl 模板文件
<html>
<head>
<linkrel="stylesheet"href="${ctx}/static/js/zTree_v3-3.5.32/css/zTreeStyle/zTreeStyle.css"type="text/css">
<scripttype="text/javascript"src="${ctx}/static/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<scripttype="text/javascript"src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.core.js"></script>
<scripttype="text/javascript"src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.excheck.js"></script>
</head>
<body>
<divid="test1"class="ztree"></div>
<inputid="roleId"value="${roleId!}"type="hidden">
<scripttype="text/javascript">
varctx="${ctx}";
</script>
<scripttype="text/javascript"src="${ctx}/static/js/role/grant.js"></script>
</body>
</html>
添加 grant.js
varzTreeObj;
$(function() {
loadModuleInfo();
});
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules"
dataType:"json",
success:function(data) {
// zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
varsetting={
data: {
simpleData: {
enable:true
}
},
view:{
showLine:false
// showIcon: false
},
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
}
};
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
}
})
}
角色授权
权限记录添加后端实现
RoleService.java
publicvoidaddGrant(Integer[]mids,IntegerroleId) {
/**
* 核心表-t_permission t_role(校验角色存在)
* 如果角色存在原始权限 删除角色原始权限
* 然后添加角色新的权限 批量添加权限记录到t_permission
*/
Roletemp=selectByPrimaryKey(roleId);
AssertUtil.isTrue(null==roleId||null==temp,"待授权的角色不存在!");
intcount=permissionMapper.countPermissionByRoleId(roleId);
if(count>0){
AssertUtil.isTrue(permissionMapper.deletePermissionsByRoleId(roleId)<count,"权限分配失败!");
}
if(null!=mids&&mids.length>0){
List<Permission>permissions=newArrayList<Permission>();
for(Integermid:mids) {
Permissionpermission=newPermission();
permission.setCreateDate(newDate());
permission.setUpdateDate(newDate());
permission.setModuleId(mid);
permission.setRoleId(roleId);
permission.setAclValue(moduleMapper.selectByPrimaryKey(mid).getOptValue());
permissions.add(permission);
}
permissionMapper.insertBatch(permissions);
}
}
RoleController.java
@RequestMapping("addGrant")
@ResponseBody
publicResultInfoaddGrant(Integer[]mids,IntegerroleId){
roleService.addGrant(mids,roleId);
returnsuccess("权限添加成功");
}
权限记录添加前端核心 js
修改 grant.js 文件 添加 ztree 复选框点击回调 onCheck 事件。
varzTreeObj;
$(function() {
loadModuleInfo();
});
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules",
dataType:"json",
success:function(data) {
// zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
varsetting={
data: {
simpleData: {
enable:true
}
},
view:{
showLine:false
// showIcon: false
},
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
},
callback: {
onCheck:zTreeOnCheck
}
};
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
}
})
}
functionzTreeOnCheck(event,treeId,treeNode) {
varnodes=zTreeObj.getCheckedNodes(true);
varroleId=$("#roleId").val();
varmids="mids=";
for(vari=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
publicList<TreeDto>queryAllModules02(IntegerroleId) {
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);
}
});
}
returntreeDtos;
}
角色拥有权限 sql 查询
<selectid="queryRoleHasAllModuleIdsByRoleId"parameterType="int"resultType="java.lang.Integer">
select module_id from t_permission where role_id=#{roleId}
</select>
ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
publicList<TreeDto>queryAllModules(IntegerroleId){
returnmoduleService.queryAllModules02(roleId);
}
权限回显前端 js
这里修改 grant.js 查询资源时传入当前选择角色 id
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules",
data:{
roleId:$("#roleId").val()
},
dataType:"json",
success:function(data) {
// zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
varsetting={
data: {
simpleData: {
enable:true
}
},
view:{
showLine:false
// showIcon: false
},
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
},
callback: {
onCheck:zTreeOnCheck
}
};
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
}
})
}
角色认证
当完成角色权限添加功能后,下一步就是对角色操作的资源进行认证操作,这里对于认证包含两块:
菜单级别显示控制
后端方法访问控制
菜单级别访问控制实现
系统根据登录用户扮演的不同角色来对登录用户操作的菜单进行动态控制显示操作,这里显示的控制使用 freemarker 指令+内建函数实现,指令与内建函数操作参考这里。
登录用户角色拥有权限查询实现
IndexController.java
/**
* 后端管理主页面
* @return
*/
@RequestMapping("main")
publicStringmain(HttpServletRequestrequest){
IntegeruserId=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
publicclassPermissionServiceextendsBaseService<Permission,Integer>{
@Autowired
privatePermissionMapperpermissionMapper;
publicList<String>queryUserHasRolesHasPermissions(IntegeruserId) {
returnpermissionMapper.queryUserHasRolesHasPermissions(userId);
}
}
PermissionMapper.java & PermissionMapper.xml
publicinterfacePermissionMapperextendsBaseMapper<Permission,Integer>{
List<String>queryUserHasRolesHasPermissions(IntegeruserId);
}
<selectid="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>
系统主页面菜单显示指令控制
这里仅显示部分菜单控制。
<#ifpermissions?seq_contains("60")>
<liclass="layui-nav-item">
<ahref="javascript:;"class="layui-menu-tips"><iclass="fa fa-gears"></i><spanclass="layui-left-nav">系统设置</span><spanclass="layui-nav-more"></span></a>
<dlclass="layui-nav-child">
<#ifpermissions?seq_contains("6010")>
<dd>
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-11"data-tab="user/index"target="_self"><iclass="fa fa-user"></i><spanclass="layui-left-nav">用户管理</span></a>
</dd>
</#if>
<#ifpermissions?seq_contains("6020")>
<ddclass="">
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-12"data-tab="role/index"target="_self"><iclass="fa fa-tachometer"></i><spanclass="layui-left-nav">角色管理</span></a>
</dd>
</#if>
<#ifpermissions?seq_contains("6030")>
<ddclass="">
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-13"data-tab="module/index"target="_self"><iclass="fa fa-tachometer"></i><spanclass="layui-left-nav">菜单管理</span></a>
</dd>
</#if>
</dl>
</li>
</#if>
后端方法级别访问控制
实现了菜单级别显示控制,但最终客户端有可能会通过浏览器来输入资源地址从而越过 ui 界面来访问后端资源,所以接下来加入控制方法级别资源的访问控制操作,这里使用 aop+自定义注解实现
自定义注解@RequirePermission
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceRequirePermission{
Stringcode()default"";
}
方法级别使用注解
@RequestMapping("list")
@ResponseBody
@RequirePermission(code="101001")
publicMap<String,Object>querySaleChancesByParams(Integerflag,HttpServletRequestrequest,SaleChanceQuerysaleChanceQuery){
if(null!=flag&&flag==1){
// 查询分配给当前登录用户 营销记录
saleChanceQuery.setAggsinMan(LoginUserUtil.releaseUserIdFromCookie(request));
}
returnsaleChanceService.queryByParamsForTable(saleChanceQuery);
}
定义aop切面类拦截指定注解标注的方法
@Component
@Aspect
publicclassPermissionProxy{
@Autowired
privateHttpSessionsession;
@Around(value="@annotation(com.xxx.sys.annotaions.RequirePermission)")
publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{
List<String>permissions=(List<String>)session.getAttribute("permissions");
if(null==permissions||permissions.size()==0){
thrownewNoPermissionException();
}
Objectresult=null;
MethodSignaturemethodSignature=(MethodSignature)pjp.getSignature();
RequirePermissionrequirePermission=methodSignature.getMethod().getDeclaredAnnotation(RequirePermission.class);
if(!(permissions.contains(requirePermission.code()))){
thrownewNoPermissionException();
}
result=pjp.proceed();
returnresult;
}
}
扩展~自定义注解实例
从 JDK5 开始,Java 增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。下面我们来看看如何自定义注解。
创建自定义注解类
packagecom.lebyte.annotations;
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Inherited;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
/* @Target,@Retention,@Inherited,@Documented
* 这四个是对注解进行注解的元注解,负责自定义的注解的属性
*/
@Target({ElementType.TYPE,ElementType.METHOD})//表示注解的作用对象,ElementType.TYPE表示类,ElementType.METHOD表示方法
@Retention(RetentionPolicy.RUNTIME)//表示注解的保留机制,RetentionPolicy.RUNTIME表示运行时注解
@Inherited//表示该注解可继承
@Documented//表示该注解可生成文档
public@interfaceDesign{
Stringauthor();//注解成员,如果注解只有一个成员,则成员名必须为value(),成员类型只能为原始类型
intdata()default0;//注解成员,默认值为0
}
使用注解
packagecom.lebyte;
importcom.lebyte.annotations.Design;
@Design(author="lebyte",data=100)//使用自定义注解,有默认值的成员可以不用赋值,其余成员都要赋值
publicclassPerson{
@Design(author="lebyte",data=90)
publicvoidlive(){
}
}
解析注解
packagecom.lebyte;
importjava.lang.annotation.Annotation;
importjava.lang.reflect.Method;
importcom.lebyte.annotations.Design;
publicclassMain{
publicstaticvoidmain(String[]args)throwsClassNotFoundException{
Classc=Class.forName("com.lebyte.Person");//使用类加载器加载类
//1、找到类上的注解
if(c.isAnnotationPresent(Design.class)){//判断类是否被指定注解注解
Designd=(Design)c.getAnnotation(Design.class);//拿到类上的指定注解实例
System.out.println(d.data());
}
//2、找到方法上的注解
Method[]ms=c.getMethods();
for(Methodm:ms){
if(m.isAnnotationPresent(Design.class)){//判断方法是否被指定注解注解
Designd=m.getAnnotation(Design.class);//拿到类上的指定注解实例
System.out.println(d.data());
}
}
//3、另外一种方法
for(Methodm:ms){
Annotation[]as=m.getAnnotations();//拿到类上的注解集合
for(Annotationa:as){
if(ainstanceofDesign){//判断指定注解
Designd=(Design)a;
System.out.println(d.data());
}
}
}
}
}