Data Rights Management Center - based on mybatis interceptor implementation

Data Rights Management Center

Since most of the company's projects use mybatis, and also use the mybatis interceptor for paging processing, technically, we also directly choose to start with the interceptor.

 

demand scenario

 

The first scenario: row-level data processing

 

original sql:

select id,username,region from sys_user ;

It needs to be packaged as:

select * from (
    select id,username,region from sys_user
) where 1=1 and region like “3210%";

explain

Users can only query the data of the current city and subordinate cities. The like part can also be a dynamic parameter (described below)

 

This scenario also includes the following:

# judge
select * from (select id,username,region from sys_user ) where 1=1 and region != 320101;
# enumerate
select * from (select id,username,region from sys_user ) where 1=1 and region in (320101,320102,320103);
...

The second scenario: column-level data processing

original sql:

select id,username,region from sys_user ;

User A can see id, username, region

User B can only view the values ​​of id and username, but has no permission to view the values ​​of region.

 

Application flow chart

images/nziPCRhXndQGpQKQNrJ5cedHSC6XBRTA.png

 

Application Link Logic Diagram

images/PyDJiBwAKCB8a7s3xJmzxifFJRYWRGez.png

 

Technical realization

 

mybatis interceptor

 

Before writing the interceptor of mybatis, let's first understand the interception target method of mybaits

 

  • 1、Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

 

  • 2、ParameterHandler (getParameterObject, setParameters)

 

  • 3、StatementHandler (prepare, parameterize, batch, update, query)

 

  • 4、ResultSetHandler (handleResultSets, handleOutputParameters)

 

Here, the prepare method of StatementHandler is selected as the interception before sql execution to encapsulate sql, and the handleResultSets method of ResultSetHandler is used as the result interception and filtering after sql execution.

 

Before sql is executed

 

PrepareInterceptor.java

/**
 * mybatis data permission interceptor - prepare
 * @author GaoYuan
 * @date 4/17/2018 9:52 AM
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class })
})
@Component
public class PrepareInterceptor implements Interceptor {
    /** 日志 */
    private static final Logger log = LoggerFactory.getLogger(PrepareInterceptor.class);

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(log.isInfoEnabled()){
            log.info( "Entering PrepareInterceptor..." );
        }
        if(invocation.getTarget() instanceof RoutingStatementHandler) {
            RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
            StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate" );
             // Get the mappedStatement property of the delegate parent class BaseStatementHandler through reflection 
            MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement" );
             // Ten million You cannot use the method annotated below, it will cause the object to be lost and the conversion will fail
             // MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; 
            PermissionAop permissionAop = PermissionUtils.getPermissionByDelegate(mappedStatement);
             if (permissionAop == null ) {
                 if (log.isInfoEnabled()){
                    log.info( "Data permission release..." );
                }
                return invocation.proceed();
            }
            if(log.isInfoEnabled()){
                log.info( "Data permission processing [splicing SQL]..." );
            }
            BoundSql boundSql = delegate.getBoundSql();
            ReflectUtil.setFieldValue(boundSql, "sql", permissionSql(boundSql.getSql()));
        }
        return invocation.proceed();
    }

    /**
     * Permission sql wrapper
     * @author GaoYuan
     * @date 4/17/2018 9:51 AM
     */
    protected String permissionSql(String sql) {
        StringBuilder sbSql = new StringBuilder(sql);
        String userMethodPath = PermissionConfig.getConfig("permission.client.userid.method" );
         // Current login person 
        String userId = (String)ReflectUtil.reflectByPath(userMethodPath);
         // If the user is 1, only the first 
        if ("1" .equals(userId)){
             // sbSql = sbSql.append(" limit 1 ");
             // if there is dynamic parameter regionCd 
            if ( true ){
                String premission_param = "regionCd";
                //select * from (select id,name,region_cd from sys_exam ) where region_cd like '${}%'
                String methodPath = PermissionConfig.getConfig("permission.client.params." + premission_param);
                String regionCd = (String)ReflectUtil.reflectByPath(methodPath);
                sbSql = new StringBuilder("select * from (").append(sbSql).append(" ) s where s.regionCd like concat("+ regionCd +",'%')  ");
            }

        }
        return sbSql.toString();
    }
}

After sql is executed

 

ResultInterceptor.java

/**
 * mybatis data permission interceptor - handleResultSets
 * filter the result set
 * @author GaoYuan
 * @date 4/17/2018 9:52 AM
 */
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})
@Component
public class ResultInterceptor implements Interceptor {
    /** 日志 */
    private static final Logger log = LoggerFactory.getLogger(ResultInterceptor.class);

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(log.isInfoEnabled()){
            log.info( "Enter ResultInterceptor..." );
        }
        ResultSetHandler resultSetHandler1 = (ResultSetHandler) invocation.getTarget();
         // Get the mappedStatement attribute value through java reflection
         // You can get the resulttype 
        MappedStatement in mybatis mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(resultSetHandler1, "mappedStatement" );
         // Get the aspect Object 
        PermissionAop permissionAop = PermissionUtils.getPermissionByDelegate(mappedStatement);

        // Execute the request method and save the result to result 
        Object result = invocation.proceed();
         if (permissionAop != null ) {
             if (result instanceof ArrayList) {
                ArrayList resultList = (ArrayList) result;
                for (int i = 0; i < resultList.size(); i++) {
                    Object oi = resultList.get(i);
                    Class c = oi.getClass();
                    Class[] types = {String.class};
                    Method method = c.getMethod("setRegionCd", types);
                    // 调用obj对象的 method 方法
                    method.invoke(oi, "");
                    if(log.isInfoEnabled()){
                        log.info( "Data permission processing [filter result]..." );
                    }
                }
            }
        }
        return result;
    }
}

Among them, PermissionAop is a custom aspect of the dao layer, which is used to switch and control whether to enable data permission filtering.

 

difficulty

 

  1. How to get the annotation content of the dao layer in the interceptor;

  2. How to get the current login ID;

  3. How to pass dynamic parameters;

  4. Need to take into account the priority of paging with sql.

 

answer

 

Interceptor gets dao layer annotations

 

The interceptor acquisition methods of different methods are slightly different, and you can view them by yourself in the code of PrepareInterceptor.java and ResultInterceptor.java above.

 

Get the current login ID

 

Due to different frameworks or different projects, the method of obtaining the logged-in person of the day may be different, so the method of obtaining the current logged-in person can only be dynamically passed to the permission center through configuration. Add to the config file:

# The client gets the current login ID
permission.client.userid.method=com.raising.sc.permission.example.util.UserUtils.getUserId

Then use the Java reflection mechanism to trigger the getUserId( ) method.

 

Pass dynamic parameters

 

For example, user A can only query all data of his own unit and its subordinate units; the sql of the where part of the configuration center configuration is as follows:

org_cd like concat(${orgCd},'%')

Then read the above sql through PrepareInterceptor.java, and configure the corresponding permissions in the permission parameter (orgCd) in advance through the method associated with the parameter [orgCd] set in the database or configuration file (similar to the method of obtaining the current login ID). Method path, parameter value type, return value type, etc.

 

The configuration file or database gets the method path corresponding to orgCd:

com.raising.sc.permission.example.util.UserUtils.getRegionCdByUserId

Of course, this is just a simple dynamic parameter now, and the rest still need follow-up development, here is just the simplest attempt.

 

expand

 

From a product point of view, this module needs to consist of three parts:

1. foruo-permission-admin data permission management platform 2. foruo-permission-server data permission server (providing permission-related interfaces) 3. foruo-permission-client data permission client (encapsulating API)

 

The content of this module can be completed in conjunction with the application link logic diagram.

 

Knowledge points involved:

 

  • Mybatis interceptor

  • Java reflection mechanism

 

Project source code

 

Code Cloud: https://gitee.com/gmarshal/foruo-sc-permission

Related content recommendation: http://www.roncoo.com/course/list.html?courseName=mybatis

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324733557&siteId=291194637