你知道权限管理的角色授权与认证吗?

权限管理中,角色授权与认证属于权限模块中的关键模块,角色授权即是将角色能够操作的菜单资源分配给指定角色的行为,角色认证即是当用户扮演指定角色登录系统后系统对于用户操作的资源进行权限校验的操作,意思这里说明白了,那么在代码中应该具体怎么实现呢?

## 角色授权与认证的方式

- 前端页面展示控制

- 后端权限访问控制

## 案例实操

### 角色授权

#### 树形数据展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUFGxU42-1600918392363)(https://imgkr.cn-bj.ufileos.com/2c68bc08-2a92-43bf-b742-da0a717b1c03.png)]

完成角色记录基本 crud 功能之后,接下来实现角色授权功能,这里实现角色授权首先完成待授权资源显示功能。对于资源的显示,这里使用开源的 tree 插件 [ztree](http://www.treejs.cn/v3/main.php#_zTreeInfo "ztree")。

##### 资源数据查询后端实现

前端 ztree 显示的资源数据格式参考<a href='http://www.treejs.cn/v3/demo.php#_102'>这里</a>。

- ModuleMapper.xml

```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

```java

public List<TreeDto> queryAllModules(){

    return moduleMapper.queryAllModules();

}

```

- ModuleController.java

```java

@RequestMapping("queryAllModules")

@ResponseBody

public List<TreeDto> queryAllModules(){

    return moduleService.queryAllModules();

}

```

##### 资源数据 ztree 显示

- role.js 添加授权点击事件

```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 添加视图转发方法

```java

@RequestMapping("toAddGrantPage")

public String toAddGrantPage(Integer roleId,Model model){

    model.addAttribute("roleId",roleId);

    return "role/grant";

}

```

- 准备显示资源数据模板

views/role 目录下添加 grant.ftl 模板文件

```html

<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

```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);

        }

    })

}

```

#### 角色授权

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ML7B1T4-1600918392367)(https://imgkr.cn-bj.ufileos.com/f8996734-3edf-4901-85b8-39aa640f25b3.png)]

##### 权限记录添加后端实现

- RoleService.java

```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

```java

@RequestMapping("addGrant")

@ResponseBody

public ResultInfo addGrant(Integer[] mids,Integer roleId){

    roleService.addGrant(mids,roleId);

    return success("权限添加成功");

}

```

##### 权限记录添加前端核心 js

修改 grant.js 文件 添加 ztree 复选框点击回调 onCheck 事件。

```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" }

                },

                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 复选框是否选择属性配置参考<a href='http://www.treejs.cn/v3/demo.php#_201'>这里</a>。

###### 资源查询后端方法实现

- ModuleService.java

```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 查询

```xml

<select id="queryRoleHasAllModuleIdsByRoleId" parameterType="int" resultType="java.lang.Integer">

    select module_id from t_permission where role_id=#{roleId}

</select>

```

- ModuleController.java

```java

@RequestMapping("queryAllModules")

@ResponseBody

public List<TreeDto> queryAllModules(Integer roleId){

    return moduleService.queryAllModules02(roleId);

}

```

###### 权限回显前端 js

这里修改 grant.js 查询资源时传入当前选择角色 id

```js

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);

        }

    })

}

```

### 角色认证

当完成角色权限添加功能后,下一步就是对角色操作的资源进行认证操作,这里对于认证包含两块:

1. 菜单级别显示控制

2. 后端方法访问控制

#### 菜单级别访问控制实现

系统根据登录用户扮演的不同角色来对登录用户操作的菜单进行动态控制显示操作,这里显示的控制使用 [freemarker](http://freemarker.foofun.cn/ "freemarker") 指令+内建函数实现,指令与内建函数操作参考<a href='http://freemarker.foofun.cn/ref_builtins_sequence.html#ref_builtin_seq_contains'>这里</a>。

##### 登录用户角色拥有权限查询实现

- IndexController.java

```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

```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

```java

public interface PermissionMapper extends BaseMapper<Permission,Integer> {

    List<String>  queryUserHasRolesHasPermissions(Integer userId);

}

```

```xml

<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>

```

##### 系统主页面菜单显示指令控制

这里仅显示部分菜单控制。

```html

<#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

```java

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface RequirePermission {

    String code() default "";

}

```

##### 方法级别使用注解

```java

@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切面类拦截指定注解标注的方法

```java

@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 增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。下面我们来看看如何自定义注解。

**创建自定义注解类**

```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

}

```

**使用注解**

```java

package com.lebyte;

import com.lebyte.annotations.Design;

@Design(author="lebyte",data=100)    //使用自定义注解,有默认值的成员可以不用赋值,其余成员都要赋值

public class Person {

    @Design(author="lebyte",data=90)

    public void live(){

    }

}

```

**解析注解**

```java

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());

                }

            }

        }

    }

}

```

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());

                }

            }

        }

    }

}

```

需要视频配套文档或更多资料+我们程序员小姐姐v:lezijie007(加好友时备注:b站-LT,不备注拒绝添加哟)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343