权限管理中,角色受权与认证属于权限模块中的关键模块,角色受权便是将角色可以操做的菜单资源分配给指定角色的行为,角色认证便是当用户扮演指定角色登陆系统后系统对于用户操做的资源进行权限校验的操做,意思这里说明白了,那么在代码中应该具体怎么实现呢?javascript
完成角色记录基本 crud 功能以后,接下来实现角色受权功能,这里实现角色受权首先完成待受权资源显示功能。对于资源的显示,这里使用开源的 tree 插件 ztree。css
前端 ztree 显示的资源数据格式参考这里。html
- 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();
}java
- ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
public List<TreeDto> queryAllModules(){
return moduleService.queryAllModules();
}node
- 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
});
}jquery
- RoleController.java 添加视图转发方法
@RequestMapping("toAddGrantPage")
public String toAddGrantPage(Integer roleId,Model model){
model.addAttribute("roleId",roleId);
return "role/grant";
}ajax
- 准备显示资源数据模板
views/role 目录下添加 grant.ftl 模板文件sql
<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>json
- 添加 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("权限添加成功");
}
修改 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);
}
这里修改 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">
<span class="layui-left-nav"> 系统设置</span> <span class="layui-nav-more"></span>
<dl class="layui-nav-child">
<#if permissions?seq_contains("6010")>
<dd>
<span class="layui-left-nav"> 用户管理</span>
</dd>
</#if>
<#if permissions?seq_contains("6020")>
<dd class="">
<span class="layui-left-nav"> 角色管理</span>
</dd>
</#if>
<#if permissions?seq_contains("6030")>
<dd class="">
<span class="layui-left-nav"> 菜单管理</span>
</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()); } } } } }