数据权限_数据权限设计 - CSDN
精华内容
参与话题
  • 通用权限管理设计 之 数据权限

    万次阅读 2018-01-04 10:09:40
    本文将对这种设计思想作进一步的扩展,介绍数据权限的设计方案。 权限控制可以理解,分为这几种 : 【功能权限】:能做什么的问题,如增加产品。 【数据权限】:能看到哪些数据的问题,如查看本人的所有订单。 ...

    前言

     

    前一篇文章《通用权限管理设计 之 数据库设计方案》介绍了【主体】- 【领域】 - 【权限】( who、what、how问题原型 ) 的设计思想

     

    本文将对这种设计思想作进一步的扩展,介绍数据权限的设计方案。

    权限控制可以理解,分为这几种 :

    【功能权限】:能做什么的问题,如增加产品。
    【数据权限】:能看到哪些数据的问题,如查看本人的所有订单。
    【字段权限】:能看到哪些信息的问题,如供应商账户,看不到角色、 部门等信息。

    上面提到的那种设计就是【功能权限】,这种设计有一定的局限性,对于主体,只能明确地指定。对于不明确的,在这里可能就没办法处理。比如下面这几种情况:

    数据仅当前部门及上级可见
    数据仅当前用户(本人)可见

    类似这样的就需要用到上面提的数据权限。

    上一篇文章我用一个表五个字段完成了【功能权限】的设计思路
    本文我将介绍如何利用一个表两个字段完成这个【数据权限】的设计思路

    初步分析

    【数据权限】是在【功能权限】的基础上面进一步的扩展,比如可以查看订单属于【功能权限】的范围,但是可以查看哪些订单就是【数据权限】的工作了。

    在设计中,我们规定好如果没有设置了数据权限规则,那么视为允许查看全部的数据。

    几个概念
    【资源】:数据权限的控制对象,业务系统中的各种资源。比如订单单据、销售单等。属于上面提到的【领域】中的一种
    【主体】:用户、部门、角色等。
    【条件规则】:用于检索数据的条件定义
    【数据规则】:用于【数据权限】的条件规则 

     
    应用场景
    1,订单,可以由本人查看 
    2,销售单,可以由本人或上级领导查看 
    3,销售单,销售人员可以查看自己的,销售经理只查看 销售金额大于100,000的。

    我们能想到直接的方法,在访问数据的入口加入SQL Where条件来实现,组织sql语句:

    1where UserID = {CurrentUserID}
    2where UserID = {CurrentUserID}  or {CurrentUserID} in (领导)
    3where UserID = {CurrentUserID}  or ({CurrentUserID} in (销售经理)  and 销售金额 > 100000)

    这些一个一个的"条件",本文简单理解为一个【数据规则】,通常会与原来我们前台的业务过滤条件合并再检索出数据。

    这是一种最直接的实现方式,在【资源】上面加一个【数据规则】(比如上面的三点)。这样设计就是

      【资源】 - 【数据规则】

    我又觉得不同的人应该对应不同的规则,那么也可以理解为,一个用户对应不同的角色,每一个角色有不一样的【数据规则】,那么设计就变成

      【资源】 - 【主体】 - 【数据规则】

    根据提供者的不同,准备不同的权限应对策略。

    这里可以简单地介绍一下,这个方案至少需要2张表,一个是  【资源,主体,规则关系表】、一个是【数据规则表】

    关系表不能直接保存角色,因为你不确定什么时候业务需要按照【部门】或者【分公司】来定义数据规则

    于是可以用  Master、MasterKey  类似这样的两个字段来确定一个【主体】

    用XML方式的话是这样配置的(放在数据库也类似):

    <?xml version="1.0" encoding="utf-8"?>
    <settings>
      <rule view="订单" role="销售人员">
          销售员 = {CurrentUserID}
      </rule>
      <rule view="订单" role="总销售经理">
         销售金额 > 100000
      </rule>
      <rule view="订单" role="区域销售经理">
        销售金额 > 100000  and 区域 = {当前用户所属区域}
      </rule>
    </settings>

    对于这种方式有兴趣的朋友也可以试一下,两种方式的【数据规则】是一样的,但是本文没有采用第二种设计方式,因为它多了一层处理逻辑,我以为应该设计越简单越好,就采用第一种方式:

      【资源】 - 【数据规则】

    当然,上面是用SQL的方式来确定条件规则的,我们当然不会这么做。SQL虽然灵活,但是我们很难去维护,也不知道SQL在我们的界面UI上面如何体现。难不成直接用一个文本框来显示。这样对应一个开发人员来说不是问题,可是对应系统管理员,很容易出问题。所以我们需要有另一方式来确定这一规则,并最终可以转换成我们的SQL语句。

    我们的设计关键在于如何规范好这些【数据规则】 ,这个规则必须是对前端友好的,而且是对后台友好的,JSON显然是很好的方式。

     规则说明:

    1,数据权限规则总是:{属性 条件 允许值}

    2,数据权限规则可以合并。比如 ( {当前用户 属于 销售人员} and {订单销售员 等于  当前用户} )   Or {当前用户  属于 销售经理}

    3,最终我们会用JSON格式

    在检索数据时会先判断有没有注册了某某【资源】的【条件规则】,如果有,那么加载这个【条件规则】并合并到我们前台的【搜索条件】(你的业务界面应该有一个搜索框吧)

     如下图定义了客户信息的搜索框,我们搜索客户ID包括AN,我们组织成的规则将会是:

    {"rules":[{"field":"CustomerID","op":"like","value":"AN","type":"string"}],"op":"and"}

    为了更好地理解【数据规则】,这里介绍一下【通用查询机制】 

    【通用查询机制】

    权限控制总离不开一些条件的限制(比如查看当前部门的单据),如果没有完善的通用查询规则机制,那么在做权限条件过滤的时候你会觉得很别扭。这里介绍一个通用查询方案,然后再介绍如何实现【数据规则】。

     

    早些时候我写过一篇关于ligerGrid结合.net设计通用处理类的文章《 jQuery liger ui ligerGrid 打造通用的分页排序查询表格(提供下载) 》。里面提到的过滤信息是直接的SQL语句。这是不可靠,而且不安全的。
    在前端传输给后台的过滤信息不应该是直接的SQL,而应该是一组过滤规则。在ligerui V1.1.8 已经加入了一个条件过滤器插件,这个插件组成的规则数据才是我受推荐的:
    比如如下

     

    {"rules":
    [
    {"field":"OrderDate","op":"less","value":"2012-01-01"},
    {"field":"CustomerID","op":"equal","value":"VINET"}
    ]
    ,"op":"and"}

     

    规则描述:
    查找顾客VINET所有订单时间小于2011-01-01的单据


    这样的数据是安全的,而且是通用的(你甚至可以再加一个OR子查询)。无论是在前端还是后台,无论你使用什么样的组件,都可以很好地利用。

    通用后台的翻译,就可以生成这样SQL的参数:

     

    Text:
    ([OrderDate] < @p1 and [CustomerID] = @p2)
    Parameters:
    p1:2012-01-01
    p2:VINET

     

    下面来点复杂的:查找 顾客VINET或者TOMSP,所有订单时间小于2011-01-01的单据

     

    {
    "rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
    "groups":[
    {"rules":[{"field":"CustomerID","op":"equal","value":"VINET"}, {"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
    ],
    "op":"and"
    }

     翻译结果:

     

    Text:([OrderDate] < @p1 and ([CustomerID] = @p2 or [CustomerID] = @p3))
    Parameters:
    p1:2012-01-01
    p2:VINET
    p3:TOMSP

     

    这个过滤规则分为三个部分:【分组】、【规则】(字段、值、操作符)、【操作符】(and or),而自身就是一个分组。
    这种简单的结构就可以满足全部的情况。

    当然,上面提到的这些条件都是在前台定义(可能是用户在搜索框自己输入的)的,而在后台,我们可能会定义一下【隐藏条件】,比如说 【员工只能查看自己的】,要怎么做呢,其实很简单,只需要在后台接收到这个过滤条件(前台toJSON,后台解析JSON)以后,再加上一个过滤规则(隐藏条件):

     

    {field:'EmployeeID',op:'equal',value:5}

     

    可以将原来的过滤规则当做一个分组加入进行:

     

     

    {op:'and',groups:[

     

    {"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
    "groups":[
    {"rules":[{"field":"CustomerID","op":"equal","value":"VINET"},{"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
    ],"op":"and"}

     

    ],rules:[{field:'EmployeeID',op:'equal',value:5}]
    }


     

    翻译如下:

     

    Text:
    ([EmployeeID] = @p1 and ([OrderDate] < @p2 and ([CustomerID] = @p3 or [CustomerID] = @p4)))
    Parameters:
    p1:5
    p2:2012-01-01
    p3:VINET
    p4:TOMSP



     这样的【条件规则】才是我们想要的,不仅在前端可以很好地解析,也可以在后台进行处理。在后台我们会定义跟这种数据结构对应的类,那么再定义一个翻译成SQL的类:


    数据权限规则

    说了这些,可以开始介绍如何实现【数据规则】了:

    上面提到的【隐藏条件】,就是我介绍的【数据规则】
    试想一些,这样 前台的过滤规则,再加上我们之间定义好的 【数据权限】控制 过滤条件。不就达到目的了吗。
    先看看我们在数据库里保存的这些【数据规则】:

     看不明白?那来个清楚一点的:

    规则描述

    订单:【订单管理员和演示角色可以查看所有的】,【订单查看员】只能查看自己的

    产品:【基础信息录入员和演示角色可以查看所有的】,【供应商】只能查看自己的

    {CurrentEmployeeID}表示当前的员工。

    实质上,我们还可以根据当前用户信息定义需要的参数,比如:

    {CurrentUserID} 当前用户Id ,对应表【CF_User】

    {CurrentRoleID} 当前角色Id ,对应表 【CF_Role】 

    {CurrentDeptID} 当前用户部门Id,对应表【CF_Department】

    {CurrentEmployeeID} 当前用户员工Id,对应表【Employees】(CF_User.EmployeeID)

    {CurrentSupplierID} 当前用户供应商Id,对应表【Suppliers】(CF_User.SupplierID)

     在数据库中我们直接保存这些用户参数,在“翻译”规则成为SQL时,会替换掉:

    比如查看订单,我们得到的SQL,可能是这样的:

    Text:     SELECT * FROM (SELECT TOP 20 * FROM (SELECT TOP 40 * FROM [Orders] WHERE ( 1=1  and ((@p1 in (@p2,@p3)) or (@p4 = @p5 and [EmployeeID] = @p6))) ORDER BY OrderID ASC) AS tmptableinner ORDER BY OrderID DESC) AS tmptableouter ORDER BY OrderID ASC     
    Parameters:
    @p1[Int32] = 7
    @p2[Int32] = 2
    @p3[Int32] = 6
    @p4[Int32] = 7
    @p5[Int32] = 7
    @p6[Int32] = 1

    {CurrentRuleID} 替换为 7

    {CurrentEmployeeID} 替换为1

     下图是我们设计【数据权限】的界面,可以选择所有的字段,包括几个用户信息:

    这些字段不仅仅只是在文本框中输入值,那么可以自定义数据来源:

     

    var fieldEditors = {};
    fieldEditors['Orders'] = {
        'ShipCity': { type: 'combobox', 
            options: {
                width: 200,
                url: "../handler/select.ashx?view=Orders&idfield=ShipCity&textfield=ShipCity&distinct=true"
            }
        }
    };

    效果界面:

     


    实际应用

    既然是数据权限控制,如果有一个统一的数据接收入口,我们倒是可以利用这个入口做一些工作。

    比如【ligerRM权限管理系统】统一使用 grid.ashx 这个数据处理程序作为列表数据的接收入口。

    有了统一的接口,方便做权限的控制,使用过 ligerGrid Javascript表格,或者类似插件的朋友,应该比较清楚服务器的交互原理。 
    在grid.ashx中,我们会通过 
    【视图/表名 】、 【排序信息】、【分页信息】、【过滤信息】
    这几个指标来获取指定的数据。


    而在实际的业务中,可能会引入权限的控制。比如某某【资源】,只能由当前用户自身才能查看,或者只能由当前用户部门及上级部门才能查看。对于这些控制,我们采用对这些可能做权限控制的【资源】注册一组【条件规则】的方式来进行。

     我们将找到view定义好的【数据权限规则】,然后和用户在前台搜索框输入的【搜索规则】合并:

    上面的代码就是数据条件合并的例子,这样便得到了我们最终需要的 过滤规则。

    结语

    本文提出了数据权限的一种实现思路,只是本人在做一个web应用时构思的方案,谈不上规范,欢迎提出你的建议意见。

    可以在http://case.ligerui.com体验实际的应用效果。

    展开全文
  • 基于SpringAOP实现数据权限控制

    万次阅读 2019-11-11 16:33:06
    基于SpringAOP实现数据权限控制 在此主要是实现对用户查询数据返回字段的控制。比如一个表格有A,B,C,D,E五列,用户U1只能查看A,B,C三列。 此文章讲述的内容并不能实现在查询时仅查询A,B,C三列,而是在查询...

    基于SpringAOP实现数据权限控制

    在此主要是实现对用户查询数据返回字段的控制。比如一个表格有A,B,C,D,E五列,用户U1只能查看A,B,C三列。

    此文章讲述的内容并不能实现在查询时仅查询A,B,C三列,而是在查询后做过滤,将D,E两列的值置为空。

    本文只启到抛砖引玉的作用,代码并没有完全实现。只写了核心部分。如果大家用到的话,还需要根据自己项目的权限体系完善。

    准备工作

    首先定义注解QueryMethod,用于标注方法是查询方法。

    /**
     * 标识此方法为查询方法,可能会受到数据权限控制,理论上所有查询方法都应该加上此注释
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface QueryMethod {
    }
    

    定义查询方法返回的结果

    /**
     * 支持过滤的结构,用于在AOP方法中对要显示的数据进行控制
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    public class FilterableResult<T> implements Filterable<T>, MetaSetter {
    
        @Getter
        @Setter
        private List<T> rows;
        private List<SysDataResource> meta;
    
        @Override
        public void doFilter(Function<T, T> filterFunc) {
            for (T row : rows) {
                filterFunc.apply(row);
            }
        }
    
        @Override
        public void setMeta(List<SysDataResource> dataResources) {
            this.meta = dataResources;
        }
    
        @Override
        public List<SysDataResource> getMeta() {
            return this.meta;
        }
    
        public static <T> FilterableResult<T> build(List<T> rows) {
            FilterableResult<T> result = new FilterableResult<>();
            result.setRows(rows);
            return result;
        }
    }
    
    /**
     * 支持过滤
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    public interface Filterable<T> {
    
        /**
         * 遍历列表,执行过滤方法
         * @param filterFunc 过滤方法
         */
        void doFilter(Function<T, T> filterFunc);
    }
    
    /**
     * 设置数据结构
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    public interface MetaSetter {
    
        /**
         * 设置数据结构,用于前台展示
         * @param dataResources 数据结构
         */
        void setMeta(List<SysDataResource> dataResources);
    
        /**
         * 获取数据结构
         * @return 数据结构
         */
        List<SysDataResource> getMeta();
    }
    

    SysDataResource为数据资源项。

    
    @Table(name = "tbsd_sys_data_resource")
    public class SysDataResource {
        /**
         * 数据ID
         */
        @Id
        @Column(name = "data_id")
        private String dataId;
    
        /**
         * 权限ID
         */
        @Column(name = "authority_id")
        private String authorityId;
    
        /**
         * 数据项名称
         */
        @Column(name = "data_name")
        private String dataName;
    
        /**
         * 数据标志符号
         */
        @Column(name = "data_code")
        private String dataCode;
    
        /**
         * 创建时间
         */
        @Column(name = "create_time")
        private Date createTime;
    
        // 扩展字段
    
        /**
         * 是否允许访问
         */
        @Column(name = "is_accessible")
        private Boolean isAccessible;
    }
    

    用户数据资源权限说明

    系统权限对应数据资源,权限中设置访问数据的业务方法。

    authorityName: 用户查询
    authorityMark: AUTH_USER_QUERY
    classAndMethod: com.wawscm.shangde.module.security.service.impl.SysUserServiceImpl.findUser(int,int)
    

    classAndMethod要明确到实现类,本文档中的方法不支持接口方法。

    用户拥有此权限后就可以设置对应的数据资源访问权限。

    资源名称 标识
    用户ID userId
    用户名 username
    密码 password
    用户姓名 name

    用户的资源权限设置如下

    资源名称 标识 isAccessible
    用户ID userId true
    用户名 username true
    密码 password false
    用户姓名 name false

    SysUser bean代码如下

    @Table(name = "tbsd_sys_user")
    public class SysUser {
        /**
         * 用户ID
         */
        @Id
        @Column(name = "user_id")
        private String userId;
    
        /**
         * 用户名
         */
        private String username;
    
        /**
         * 密码
         */
        private String password;
    
        /**
         * 用户姓名
         */
        private String name;
    
        /**
         * 手机号
         */
        @Column(name = "phone_num")
        private String phoneNum;
    
        /**
         * 用户状态(1-正常;2-冻结)
         */
        @Column(name = "user_state")
        private String userState;
    
        /**
         * 用户类型(1-系统管理员;2-分店管理员;3-便利店管理员;4-普通用户)
         */
        @Column(name = "user_type")
        private String userType;
    
        /**
         * 店铺ID(总部用户字段为空)
         */
        @Column(name = "store_id")
        private String storeId;
    
        /**
         * 最后一次登陆时间
         */
        @Column(name = "last_login_time")
        private Date lastLoginTime;
    
        /**
         * 创建时间
         */
        @Column(name = "create_time")
        private Date createTime;
    }
    

    拦截方法过滤数据

    主要根据SysDataResource.isAccessible来判断是否有字段的访问权限,如果值为false则认为没有权限,其他字段不管,因为数据的权限控制,可能只是控制某几个字段,而不是全部。比如一些id类的字段。我们不希望在设置数据资源时还要设置表格中并不显示的字段。

    核心代码如下。

    /*
     * Copyright © 2016-2018 WAWSCM Inc. All rights reserved.
     */
    package com.wawscm.shangde.interceptor;
    
    import com.wawscm.shangde.base.Filterable;
    import com.wawscm.shangde.base.MetaSetter;
    import com.wawscm.shangde.base.SystemSettings;
    import com.wawscm.shangde.module.security.helper.UserAuthorityHelper;
    import com.wawscm.shangde.module.security.model.SysDataResource;
    import com.wawscm.shangde.utils.ShiroUtil;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import java.beans.PropertyDescriptor;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 数据权限拦截器
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    @Component
    @Aspect
    public class DataResourceAuthorityInterceptor {
    
        @Autowired
        private UserAuthorityHelper userAuthorityHelper;
    
        @Autowired
        private SystemSettings systemSettings;
    
        /**
         * 切入点设置,拦截所有具有{@link com.wawscm.shangde.annotation.QueryMethod}注解的方法
         */
        @Pointcut("@annotation(com.wawscm.shangde.annotation.QueryMethod)")
        public void queryMethodPointcut() {
        }
    
        /**
         * 环绕通知
         * @param joinPoint ProceedingJoinPoint
         * @return 方法返回的对象
         * @throws Throwable 方法执行时抛出的异常,此处不做任何处理,直接抛出
         */
        @Around(value = "queryMethodPointcut()")
        public Object doInterceptor(ProceedingJoinPoint joinPoint) throws Throwable {
            Object object = joinPoint.proceed();
            String methodName = this.getMethodName(joinPoint);
            if (object != null) {
                if (object instanceof Filterable) {
                    this.doFilter((Filterable) object, methodName);
                }
    
                if (object instanceof MetaSetter) {
                    this.metaHandler((MetaSetter)object, methodName);
                }
            }
            return object;
        }
    
        /**
         * 执行过滤操作
         * @param filterable 方法返回的对象
         * @param methodName 拦截的方法名称
         */
        private void doFilter(Filterable<?> filterable, String methodName) {
            List<SysDataResource> resources = this.getDataResources(methodName);
    
            // 如果
            if (CollectionUtils.isEmpty(resources)) {
                return;
            }
    
            filterable.doFilter(o -> {
                Map<String, SysDataResource> dataColumnMap = new HashMap<>(resources.size());
                for (SysDataResource column : resources) {
                    dataColumnMap.put(column.getDataCode(), column);
                }
    
                PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(o.getClass());
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    String name = propertyDescriptor.getName();
                    SysDataResource dataColumn = dataColumnMap.get(name);
                    if (dataColumn != null && !dataColumn.getIsAccessible()) {
                        try {
                            propertyDescriptor.getWriteMethod().invoke(o, new Object[] {null});
                        } catch (Exception ex) {
                            // skip
                        }
                    }
                }
                return o;
            });
        }
    
        /**
         * 设置数据结构
         * @param metaSetter 方法返回的对象
         * @param methodName 拦截的方法名称
         */
        private void metaHandler(MetaSetter metaSetter, String methodName) {
            List<SysDataResource> resources = this.getDataResources(methodName);
            if (resources != null) {
                metaSetter.setMeta(resources);
            } else { // 如果没有设置数据资源,默认用户拥有访问全部资源的权限
                List<SysDataResource> allResources = findAuthorityDataResource(methodName);
                metaSetter.setMeta(allResources);
            }
        }
    
        /**
         * 根据方法名和用户ID获取用户的数据权限
         * @param methodName 拦截的方法名称
         * @return 用户的数据权限
         */
        private List<SysDataResource> getDataResources(String methodName) {
            String userId = ShiroUtil.getUserId();
            return this.userAuthorityHelper.getDataResource(methodName, userId);
        }
    
        /**
         * 获取此方法对应的所有数据资源项
         * @param methodName 拦截的方法名称
         * @return 用户的数据权限
         */
        private List<SysDataResource> findAuthorityDataResource(String methodName) {
            return null; // 此处代码省略
        }
    
        private String getMethodName(ProceedingJoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            // systemSettings.isSupportMethodParams()表示是否支持方法参数,默认支持。如果设置为不支持,则权限中的方法应设置为com.wawscm.shangde.module.security.service.impl.SysUserServiceImpl.findUser
            if (systemSettings.isSupportMethodParams() && signature instanceof MethodSignature) {
                MethodSignature methodSignature = (MethodSignature)signature;
    
                StringBuilder sb = new StringBuilder();
    
                sb.append(methodSignature.getDeclaringTypeName());
                sb.append(".");
                sb.append(methodSignature.getName());
                sb.append("(");
                Class<?>[] parametersTypes = methodSignature.getParameterTypes();
                for (int i = 0; i < parametersTypes.length; i++) {
                    if (i > 0) {
                        sb.append(",");
                    }
                    Class<?> parametersType = parametersTypes[i];
                    sb.append(parametersType.getSimpleName());
                }
                sb.append(")");
                return sb.toString();
            } else {
                StringBuilder sb = new StringBuilder();
                sb.append(signature.getDeclaringTypeName());
                sb.append(".");
                sb.append(signature.getName());
                return sb.toString();
            }
        }
    }
    
    

    UserAuthorityHelper代码如下,此处的数据均为模拟数据。正确的做法应该是从数据库或缓存中获取

    /**
     * 用户权限工具类
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    @Component
    public class UserAuthorityHelper {
    
        public List<SysDataResource> getDataResource(String methodName, String userId) {
            List<SysDataResource> resources = new ArrayList<>();
            SysDataResource resource1 = new SysDataResource();
            resource1.setDataCode("userId");
            resource1.setDataName("用户ID");
            resource1.setIsAccessible(true);
    
            SysDataResource resource2 = new SysDataResource();
            resource2.setDataCode("username");
            resource2.setDataName("用户名");
            resource2.setIsAccessible(true);
    
            SysDataResource resource3 = new SysDataResource();
            resource3.setDataCode("password");
            resource3.setDataName("密码");
            resource3.setIsAccessible(false);
    
            SysDataResource resource4 = new SysDataResource();
            resource4.setDataCode("name");
            resource4.setDataName("用户姓名");
            resource4.setIsAccessible(false);
    
            resources.add(resource1);
            resources.add(resource2);
            resources.add(resource3);
            resources.add(resource4);
    
            return resources;
        }
    }
    
    

    SysUserServiceImpl代码如下,此处的数据也是模拟数据

    /**
     * 用户业务
     *
     * @author Wang Chengwei
     * @since 1.0.0
     */
    @Service
    public class SysUserServiceImpl implements SysUserService {
    
    
        @Override
        @QueryMethod
        public FilterableResult<SysUser> findUser(int page,  int pageNum) {
            List<SysUser> users = new ArrayList<>();
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
            users.add(mockUser());
    
            System.out.println("返回的数据");
            System.out.println(JsonKit.toJson(users));
            return FilterableResult.build(users);
        }
    
        private SysUser mockUser() {
            SysUser sysUser = new SysUser();
            sysUser.setUserId(UUIDGenerator.genertate());
            sysUser.setUsername(UUIDGenerator.genertate());
            sysUser.setName(UUIDGenerator.genertate());
            sysUser.setPassword(UUIDGenerator.genertate());
            sysUser.setPhoneNum(UUIDGenerator.genertate());
            sysUser.setCreateTime(new Date());
            sysUser.setLastLoginTime(new Date());
    
            return sysUser;
        }
    }
    

    测试

    public class SysUserServiceImplTest extends BaseSpringTestCase {
    
        @Autowired
        private SysUserService sysUserService;
    
        @Test
        public void findUser() {
            FilterableResult<SysUser> users = this.sysUserService.findUser(1, 15);
            System.out.println("过滤后的数据");
            System.out.println(JsonKit.toJson(users));
        }
    }
    

    过滤前的数据为

    {
        "rows": [
            {
                "userId": "838563855e3e44489d6dc91c8a37031a",
    	        "username": "b6f89f7ec27e434e92638a063b310a66",
    	        "password": "0ec85df1f31f4d88b9efbb62c46863f9",
    	        "name": "3cf146b6f13c46ef9372c19f734fa712",
    	        "phoneNum": "e42d86e8212943a7926515cc5aaf0dab",
    	        "lastLoginTime": "2018-01-05 18:47:52",
    	        "createTime": "2018-01-05 18:47:52"
            },
            {
                "userId": "8cfcd7ccaa3442edb8c4175e5e4e7e9e",
    	        "username": "632f1491d576486bb936d7da8ddf1bf6",
    	        "password": "acc506932c194adf963de57a3f651ac6",
    	        "name": "dfa65420b26f4222abc3e4477ec0efc4",
    	        "phoneNum": "619e24618a894368b3d3f4a242bc9a81",
    	        "lastLoginTime": "2018-01-05 18:47:52",
    	        "createTime": "2018-01-05 18:47:52"
            }
            ......
        ]
    }
    

    过滤后的数据为

    {
        "rows": [
            {
                "userId": "838563855e3e44489d6dc91c8a37031a",
                "username": "b6f89f7ec27e434e92638a063b310a66",
                "phoneNum": "e42d86e8212943a7926515cc5aaf0dab",
                "lastLoginTime": "2018-01-05 18:47:52",
                "createTime": "2018-01-05 18:47:52"
            },
            {
                "userId": "8cfcd7ccaa3442edb8c4175e5e4e7e9e",
                "username": "632f1491d576486bb936d7da8ddf1bf6",
                "phoneNum": "619e24618a894368b3d3f4a242bc9a81",
                "lastLoginTime": "2018-01-05 18:47:52",
                "createTime": "2018-01-05 18:47:52"
            }
            ......
        ],
        "meta": [
            {
                "dataName": "用户ID",
                "dataCode": "userId",
                "isAccessible": true
            },
            {
                "dataName": "用户名",
                "dataCode": "username",
                "isAccessible": true
            },
            {
                "dataName": "密码",
                "dataCode": "password",
                "isAccessible": false
            },
            {
                "dataName": "用户姓名",
                "dataCode": "name",
                "isAccessible": false
            }
        ]
    }
    

    从结果上可以看出password,name这两个字段已经被过滤掉了,同时增加了meta数据结构内容。前台可以根据meta中返回的数据来创建表格,实现表格的动态显示。


    原创文章,转载请注明出处!

    展开全文
  • 数据权限系统

    万次阅读 2018-04-08 19:12:08
    在之前写过一篇关于菜单权限系统的设计,所以为了完善整个权限系统的模型,决定把数据权限也做一个总结。菜单权限管理系统目标实现对数据的权限控制。简单的来说,就是决定谁可以操作(增删改查)哪些数据。该权限...

    在之前写过一篇关于菜单权限系统的设计,所以为了完善整个权限系统的模型,决定把数据权限也做一个总结。菜单权限管理系统

    目标

    实现对数据的权限控制。简单的来说,就是决定谁可以操作(增删改查)哪些数据。

    该权限模型的适用范围

    该模型目前在一些常用的管理平台得到的验证与实施,目前已应用在多个公司产品中,主要场景是CRM类项目。

    核心难点

    数据权限的主要难度在于查询的性能、授权、鉴权,字段级别的控制。

    比如在百万级别规模时,mysql如何实现秒级别的查询?

    场景介绍:在之前设计该权限模型时,我们的项目遇到了一个核心的难题,sql的查询速度非常的缓慢!!!一个sql可能高达10S+。这对于用户而言,是无法接受的。所以我们尝试去优化sql的表设计,索引的优化,但是最终结果都摆阵下来。主要的问题是我们并没有足够抽象数据,导致百万级别的数据下,sql非常缓慢。这里面占时不需要考虑分库分表(百万级别的规模不足以使用该手段)。

    百万级别的数据权限怎么授予用户?

    场景介绍:面对如此大的数据量,如何授权用户权限呢?总不能是逐个分配的!!!

    如何快速高效的进行数据鉴权呢?

    场景介绍:这么大的数据量,在程序中不容易处理,必须借助于数据库的高效查询方案,但是怎么实现呢?

    如何实现字段级别的权限控制?

    场景介绍:字段级别的权限怎么控制?控制sql的生成逻辑?还是怎么处理呢?

    权限模型的抽象

    有了那么多的问题,我们先从权限模型入手,我们必须高度抽象化我们的权限系统,才有机会做到。

    下面我们直接贴出一个图,介绍该模型的一些核心概念


    我们根据上图所示,可以观察到下面的几个元素,分别是:权限实体部门角色菜单权限

    其中菜单权限参考上面的链接

    权限实体:这个是该权限模型的核心元素,也就是我们需要系统控制的数据。比如客户,订单,渠道,区域等。

    部门:这个简单,就是一般的部门组织结构

    角色:抽象的实体,用于快速分配权限相同权限给一批用户,与菜单权限的角色概念一致。

    权限的查询模型

    在上面的图中,不知道大家是否看到了一条sql

    select xxx from xx_table where account in (xxx) and product_group in (xxx) and country in (xxx)

    没错,这就是该模型的查询模型。其中account,product_group,country代表需要做权限控制的权限实体。

    在这里面只是一个简单的模型,如果account或者country等数据量足够大时,我们会发现mysql in查询是会出现非常严重的查询性能的。比如account数据量规模为100W时...

    引入部门组织结构,突破数据量的限制

    还是看上图,我们有一个部门组织结构,我们可以想象下,比如总经理,自然是可以查看所有部门的数据。假设A部门经理,可以查看A部门以及子部门的数据。如果是普通的员工,只能查看自己账号下的数据。

    下面我们给出一个部门组织结构图,并且给每个部分标志一个key,key的生成规则如下

    1.根部门的key为数字10

    2.根部门的直属部门的key为10 001,10 002,10 003...以此类推

    3.其他的key生成逻辑和步骤2相同,唯一不同的就是key的长度变长了,比如:10 001 001,10 001 002

    这里面我们给部门打上如下的key,为了方便介绍,我们还是贴图片


    有了该模型,我们结合mysql的前置查询索引的特性(org like 'xxxx%')

    where org like '10%'
    org like '10001%'
    org like '10002%'
    上面的查询,在mysql下,200W的数据规模,可以实现1S内响应!!!

    如何授权

    解决了查询的性能,其实授权也就很简单了。

    授权我们一般根据员工的岗位来设定,普通员工,部门经理

    普通员工:不需要单独授权,直接绑定员工工号即可

    部门经理:设置该员工可以查看的部门(支持多个)

    如何鉴权

    鉴权和授权一般是相互的一个过程

    1.判断该员工是否为部门经理

    2.如果是,取出部门的key

    3.添加到sql中,使用mysql前置like查询数据

    字段权限的控制

    字段权限的控制,我们直接针对接口编程,不要针对sql编程。

    比如获取员工列表:我们只需要控制输出的字段属性即可。

    为啥不针对sql编程?因为sql需要考虑各种join,字段是否缺失,关联的内容存储在其他表等情况

    数据库的实现

    1.识别权限实体

    2.给权限实体加上一个工号字段,一个部门字段,比如:emp_id,org_key

    3.查询时,先获取员工的角色权限,如果是部门经理,那么获取对应的key列表

    4.构建sql,加入部门或者工号的sql片段

    • org_key lik '10 001%' or org_key like '10 002%',这是部门的sql
    • emp_id = 'xxx',这是普通员工的sql

    总结

    设计好一个权限系统,说难不难,但是也不简单。

    主要的难点在于

    1.识别出你需要控制的数据(权限实体)

    2.高效的查询效率与sql的索引相结合(这里面使用部门key与sql的前置查询)

    应用于扩展

    该模型设计简单,具备一定的普遍性,所以在设计时,我们可以根据不同的系统去进行演进与扩展。大家有兴趣的可以思考下

    展开全文
  • 通用数据权限的设计思路

    千次阅读 2020-01-02 09:25:30
    根据目前的调研情况,有两种数据级别权限设计思路,都可以实现对人员访问的数据权限控制,从而实现不同的人员能够看到不同的数据,例如经理能够看到其部门下所有人的数据,而单个的员工只能看到自己的数据。...

    接着上个襄阳项目的需要, 目前的项目情况是,一期已经把功能权限做完了,可以对不同用户的不同权限功能做到限制,现在需要做数据的权限,不同的用户看到不同的数据。

    根据目前的调研情况,有两种数据级别权限设计思路,都可以实现对人员访问的数据权限控制,从而实现不同的人员能够看到不同的数据,例如经理能够看到其部门下所有人的数据,而单个的员工只能看到自己的数据。用户拥有的权限越大,能看到的数据就越多。

    第一种是使用先在网关进行权限的判断,此用户有这个操作的权限,再进行查询,查询时根据参数进行SQL的拼接。网关鉴定是否能访问以及转发请求,对SQL的拼接和处理在各微服务自己内部进行,服务A/B/C都需要自己写需要查询的参数。如下图所示

    使用此方式存在一些弊端 ,使用SQL拼接的方式不利于统一管理权限,每个项目有自己的过滤方式,拼接SQL的方式,不利于统一管理,也不方便后期可能出现的规则修改,因为已经硬编码到代码中,需要各个服务自己修改一遍。

    优点很明显,足够很简单,只需要根据相应的规则调整SQL就可以了。

     

    第二种方式,专门抽离一个权限鉴定的服务,所有到达网关的请求后,网关去调用权限鉴定服务,进行访问的URL的权限判断,如果鉴定成功,则能够访问,网关转到请求到对应的服务,否则网关直接返回,提示用户没有权限访问。数据权限的控制依然需要各个微服务硬编码到各自的项目代码中去。如下图所示

     

    目前没有什么方式能做这种通用的数据级权限控制(因为涉及到各个服务的查询SQL进行过滤,拼接,在这个权限鉴定服务中没法实现这样的功能,对各种不同的查询的SQL进行拼接,过滤)。

     

    这种方式相对于第一种把对接口的访问权限校验抽离出来了,减轻了网关的压力,依然是各个服务自己实现数据查询的控制。

     

    现在的思路是: 

    新建一个mybatis的插件, 让各个服务都依赖这个(java项目可以继承, 其他的php得服务暂时不考虑), 这个插件的作用就是对

    SQL进行过滤, 拼接. 鉴权服务会对配置的数据的权限进行读取, 形成一个SQL, 以传递参数的形式, 返回给网关, 网关转发给其他的微服务, 服务获取到携带的SQL参数, 利用插件对SQL进行拼接, 过滤, 就可以查询出来数据了.进行数据的返回.

     

    下面是目前的一部分代码, 希望能够给各位一些启示:

    package co.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.springframework.stereotype.Component;
    
    import java.util.Properties;
    
    /**
     * @author zhangke
     * @time 2019年12月23日10:10:35
     */
    @Component
    @Slf4j
    @Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
            @Signature(type = Executor.class,//这是指拦截哪个接口
                    method = "query",//这个接口内的哪个方法名,不要拼错了
                    //这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    })
    public class AuthorityFilterPlugin implements Interceptor {
    
        /**
         * 这里是每次执行操作的时候,都会进行这个拦截器的方法内
         *
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //对sql进行处理
            Object[] queryArgs = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
            Object parameter = queryArgs[1];
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            // 获取到SQL ,进行调整
            String sql = boundSql.getSql();
            if (!sql.contains("limit")) {
                sql = sql + "limit 1";
            }
            log.info("正在执行的sql是: {}", sql);
            return invocation.proceed();
        }
    
        /**
         * 主要是为了把这个拦截器生成一个代理放到拦截器链中
         */
        @Override
        public Object plugin(Object target) {
            //官方推荐写法
            return Plugin.wrap(target, this);
        }
    
    
        /**
         * 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
         *
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
        }
    
    
    }

     

    下面是过滤的参数的设置.

    package com.curefun.authority.filter;
    
    
    import lombok.Data;
    import java.util.List;
    
    /**
     * 过滤器的参数实体类,用于拼接SQL用的
     *
     * question:
     * 1.分页怎么办? mybatis的分页插件之前执行...
     * 2.夺标关联查询怎么办? 别名的设置?
     * 3.这是单独的一个服务,获取到的sql, 可以用拼接url的形式转发给其他服务,其他服务又需要改造,添加mybatis 的插件.
    4.一个查询方法对应的多个sql的时候,拼接哪一个?怎么指定?
     *
     */
    @Data
    public class FilterVO {
    
    
        private String id;
        /**
         * 服务名
         */
        private String serviceName;
        /**
         * 请求的url
         */
        private String requestUrl;
        /**
         * 类型 0:不启用   1:启用
         */
        private Integer filterState;
        /**
         * 参数的具体值
         */
        private List<ColumnDataVO> params;
    
    
    }
    
    
    package com.curefun.authority.filter;
    
    import lombok.Data;
    
    
    /**
     * 一个查询的所有要拼接的参数
     */
    @Data
    public class ColumnDataVO<T> {
    
        /**
         * 列名字
         */
        private String columnName;
        /**
         * 列值,String,Number,List,Set,类型,或者Pair的类型
         */
        private T value;
        /**
         * 类型
         */
        private OperationEnum operationEnum;
        /**
         * 参数类型
         */
        private ParamTypeEnum paramEnum;
        /**
         * 排序,从0 开始,数字越小拼接就越靠前,不能为空
         */
        private int orderNum;
    
    
        public ColumnDataVO(String columnName, T value, OperationEnum operationEnum, ParamTypeEnum paramEnum, int orderNum) {
            this.columnName = columnName;
            this.value = value;
            this.operationEnum = operationEnum;
            this.paramEnum = paramEnum;
            this.orderNum = orderNum;
        }
    
        public ColumnDataVO() {
        }
    
    }
    
    
    package com.curefun.authority.filter;
    
    /**
     * 操作类型化的美剧
     */
    public enum OperationEnum {
        EQUAL(1,"=","相等"),
        GT(2,">","大于"),
        LT(3,"<","小于"),
        GTE(4,">=","大于等于"),
        LTE(5,"<=","小于等于"),
        IN(6,"in","包含"),
        BETWEEN(7,"between","介于之间"),
        ;
    
        private int typeCode;
        private String operationSymbol;
        private String message;
    
        OperationEnum(int typeCode, String operationSymbol, String message) {
            this.typeCode = typeCode;
            this.operationSymbol = operationSymbol;
            this.message = message;
        }
    
    
        public int getTypeCode() {
            return typeCode;
        }
    
        public String getOperationSymbol() {
            return operationSymbol;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    
    
    package com.curefun.authority.filter;
    
    /**
     * 参数类型
     */
    public enum ParamTypeEnum {
    
    
        STRING(1,"字符串"),
        NUMBER(2,"数字"),
        LIST(3,"List"),
        SET(4,"Set"),
        /**
         * HH:mm:ss
         */
        TIME(5,"时间"),
        /**
         * yyyy-MM-dd HH:mm:ss
         */
        DATEtIME(6,"时间"),
        /**
         * 双值,用于between的前后范围
         */
        PAIR(7,"双值"),
        ;
    
        private int type;
        private String message;
    
        ParamTypeEnum(int type, String message) {
            this.type = type;
            this.message = message;
        }
    
        public int getType() {
            return type;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    

     

    未完成的部分:

    1.根据FilterVO 做SQL的拼接.

    2.以上的四个问题,有待于解决,

    3.mybatis的过滤器中,怎么根据sql进行正确的拼接, 而且需要考虑效率问题.

    5.系统配置的表设计,我这里没做,我想过一下, 但是没有把表设计出来,其实是个一对多的表设计, 两张表就够了.

     

    好了, 就写到这里, 之所以不往下写了, 是因为业务暂时没有这方面的需求, 我这个只是属于前期的探索, 这个已经完成的项目, 还有很大的变数, 还未交付. 所以不做了. 好好复习.

    这里放一个别人设计的一张表结构: 

    CREATE TABLE `sys_acl_data` (
      `id` int(11) NOT NULL,
      `acl_id` int(11) NOT NULL COMMENT '对应权限表主键',
      `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:可用,0:不可用',
      `param` varchar(20) NOT NULL DEFAULT '' COMMENT '参数',
      `operation` int(11) NOT NULL DEFAULT '0' COMMENT '操作类型,0;等于,1:大于,2:小于,3:大于等于,4:小于等于,5:包含,6:介于之间,。。。',
      `value1` varchar(100) NOT NULL DEFAULT '0',
      `value2` varchar(100) NOT NULL DEFAULT '0',
      `next_param_op` int(11) NOT NULL DEFAULT '0' COMMENT '后续有参数时连接的关系,0:没有其他参数控制,1:与&&,2:或||',
      `seq` tinyint(4) NOT NULL DEFAULT '0' COMMENT '顺序',
      PRIMARY KEY (`id`),
      KEY `idx_acl_id` (`acl_id`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='数据权限表';

    有可取之处, 不过要想在实际生产环境中适用, 还有很多的工作需要做, 

     

    如果您觉得写得不多, 可以请作者喝一杯咖啡

     

     

     

    展开全文
  • 数据权限设计研究-行数据权限

    万次阅读 2019-10-21 12:08:32
    数据权限设计研究-行数据权限关于权限设计功能权限数据权限前提数据分类几种场景设计方案与思路映射表提供过滤sql的方法测试实际应用查询新增修改删除修改数据的私有,公开,部门属性私有改为部门私有改为公开部门改...
  • 功能权限和数据权限

    万次阅读 2017-11-10 16:58:20
    权限分为数据权限和功能权限 1、功能权限:  能不能打开某一个界面,能不能触发一个界面上的一个按钮,某些业务员能不能删除订单,采购员能不能删除业务员某个销售订单,带着一系列问题?这是什么问题。没错这就是...
  • 浅谈数据权限

    万次阅读 2017-04-23 16:47:25
    数据权限参考
  • 数据权限设计思路

    万次阅读 2019-07-08 11:02:11
    https://www.cnblogs.com/landeanfen/p/7760803.html https://www.cnblogs.com/leoxie2011/archive/2012/03/20/2408542.html
  • 数据权限设计(转载)

    万次阅读 2019-01-01 13:58:14
    几乎在任何一个系统中,都离不开权限的设计,权限设计 = 功能权限 + 数据权限,而功能权限,在业界常常是基于RBAC(Role-Based Access Control)的一套方案。而数据权限,则根据不同的业务场景,则权限却不尽相同,...
  • 电脑重装之后,安装QQ,结果出现报错: 安装路径无效,您没有权限在此位置写入数据,请重新选择 上网查了
  • 数据权限的设计与实现

    万次阅读 2015-12-08 19:25:00
    1.权限分为菜单权限,操作权限,数据权限, 菜单权限即不同用户能够看到的菜单按钮不同,如系统管理员能看到系统管理,用户管理等菜单,而普通用户是看不到这些管理菜单的。 操作权限即为不同用户能够对列表进行的...
  • JAVA 数据权限设计

    万次阅读 2014-07-17 09:34:20
    在各种系统中,要保证数据对象的安全性以及易操作性,使企业的各业务部门、职能部门能够方便而且高效的协同工作,那么一个好的数据权限管理设计就成为一个关键的问题。虽然企业中各个单元的工作流程有所不同,处理的...
  • mysql数据导出权限问题

    万次阅读 2014-11-12 16:58:15
    mysql数据导出的方法有很多,例如mysqldump, mysql -e 'sql' > file, 这些都可以很方便的导出数据,可是在使用普通用户导出数据的时候,出现了问题。 1 select * into outfile "file_...
  • 通用数据权限管理系统设计

    万次阅读 热门讨论 2006-08-29 16:16:00
    通用数据权限管理系统设计(一) 作者:逸云 前言: 本文提供一种集成功能权限和数据权限的解决方法,以满足多层次组织中权限管理方面的集中控制。本方法是RBAC(基于角色的访问控制方法)的进一步扩展和延伸,即...
  • 1 数据级的权限管理和功能级的权限管理  引自:http://www.iteye.com/problems/97374 功能级权限,有大有小。大的可以直接包括一个业务模块,小的可以是一个按钮。一般的功能级权限一般包括:菜单、url、按钮 。 ...
  • SpringCloud微服务权限控制(一)概述

    万次阅读 多人点赞 2018-09-27 18:11:01
    从单体应用到SOA应用再到Spring Cloud微服务构架,应用的安全访问都是非常重要的问题,怎么样设计微服务的权限控制?首先,权限控制可以分为三个部分:用户认证,服务权限,用户权限。 用户认证 用户认证,简单的...
  • shiro的RequiresPermissions注解使用

    万次阅读 2020-08-29 14:18:32
    权限控制是shiro最核心的东西 Shiro权限声明通常是使用以冒号分隔的表达式。一个权限表达式可以清晰的指定资源类型,允许的操作,可访问的数据。同时,Shiro权限表达式支持简单的通配符,可以更加灵活的进行权限...
  • 1.创建新用户create user ...2.给新用户授权GRANT CREATE USER,DROP USER,ALTER USER ,CREATE ANY VIEW , DROP ANY VIEW,EXP_FULL_DATABASE,IMP_FULL_DATABASE, DBA,CONNECT,RESOURCE,CREATE SESSION TO 用户名;
  • 数据安全之用户权限

    万次阅读 2020-05-08 11:46:03
    当我们在做多用户系统时,对于用户的编辑删除操作,一定需要主要对用户ID进行权限判断再进行删除处理.避免出现漏洞造成数据丢失
  • 在很多应用行业里面,都对数据的权限做了特别的声明,如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制,对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如默认只能看本...
1 2 3 4 5 ... 20
收藏数 1,124,291
精华内容 449,716
关键字:

数据权限