Springboot+mybatis plus + Annotation for Data Permission Isolation

  • 2021-10-24 22:44:30
  • OfStack

Catalog 1. Create annotations
2. Implementation

1. Create comments

When this annotation is typed on the class, there is no need to pass parameters, and all query interfaces under the class turn on data isolation; Data isolation is turned on by default on the method, and validation is turned off if the parameter is false


/**
 *  Data permission validation annotation 
 * @author xiaohua
 * @date 2021/6/23
 */
@Documented
@Target({METHOD, ANNOTATION_TYPE, TYPE})
@Retention(RUNTIME)
public @interface DataPermission {
    /**
     *  Do you want to isolate data permissions 
     */
    boolean isPermi() default true;
}

2. Implementation


@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataPermissionInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(DataPermissionInterceptor.class);

    @Autowired
    private TokenService tokenService;

    // Scanned package path (according to your own project path), here is the package path in the taken configuration 
    @Value("${permission.package-path}")
    private String packagePath;

    private final static String DEPT_ID = "dept_id";

    private final static String USER_ID = "create_user";

    private static List<String> classNames;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            LoginInfo user = tokenService.getLoginInfo();
            if (user == null){
                return invocation.proceed();
            }

            List<Long> deptIds = (List<Long>) Convert.toList(user.getDataScope());
            if (deptIds == null){
                deptIds = new ArrayList<>();
            }
            // Reflection scanning packets will be slow, so there is a lazy load here 
            if (classNames == null) {
                synchronized (LazyInit.class){
                    if (classNames == null){
                        // Scan all classes containing the specified annotations under the specified package path 
                        Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(packagePath, DataPermission.class);
                        if (classSet == null && classSet.size() == 0){
                            classNames = new ArrayList<>();
                        } else {
                            // Get the full name of the class 
                            classNames =  classSet.stream().map(Class::getName).collect(Collectors.toList());
                        }
                    }
                }
            }

            //  Get mybatis Adj. 1 Objects 
            StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

            // mappedStatement.getId() For the execution of mapper The full pathname of the method ,newId For the execution of mapper The full name of the class of the method 
            String newId = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));
            //  If it is not the specified method, end the interception directly 
            if (!classNames.contains(newId)) {
                return invocation.proceed();
            }
            String newName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());
            // Do you want to turn on data permissions 
            boolean isPermi = true;
            Class<?> clazz = Class.forName(newId);
            // Traversal method 
            for (Method method : clazz.getDeclaredMethods()) {
                // Method contains DataPermission Annotation, if it contains annotations, filter the data results 
                if (method.isAnnotationPresent(DataPermission.class) && newName.equals(method.getName())) {
                    DataPermission dataPermission =  method.getAnnotation(DataPermission.class);
                    if (dataPermission != null) {
                        // Do not validate 
                        if (!dataPermission.isPermi()) {
                            isPermi = false;
                        } else { // Enable authentication 
                            isPermi = true;
                        }
                    }
                }
            }

            if (isPermi){
                //  Get to the original sql Statement 
                String sql = statementHandler.getBoundSql().getSql();

                //  Parses and returns the new SQL Statement, processing only queries sql
                if (mappedStatement.getSqlCommandType().toString().equals("SELECT")) {
    //                    String newSql = getNewSql(sql,deptIds,user.getUserId());
                    sql = getSql(sql,deptIds,user.getUserId());
                }
                //  Modify sql
                metaObject.setValue("delegate.boundSql.sql", sql);
            }
            return invocation.proceed();
        } catch (Exception e){
            logger.error(" Data permission isolation exception: ", e);
            return invocation.proceed();
        }

    }
    
    
    /**
     *  Analyse SQL Statement and returns the new SQL Statement 
     *  Note that this method uses the JSqlParser To operate SQL That dependency package Mybatis-plus Has been integrated. If you want to use it alone, please import the dependencies yourself first 
     *
     * @param sql  Original SQL
     * @return  New SQL
     */
    private String getSql(String sql,List<Long> deptIds,Long userId) {

        try {
            String condition = "";
            String permissionSql = "(";
            if (deptIds.size() > 0){
                for (Long deptId : deptIds) {
                    if ("(".equals(permissionSql)){
                        permissionSql = permissionSql + deptId;
                    } else {
                        permissionSql = permissionSql + "," + deptId;
                    }
                }
                permissionSql = permissionSql + ")";
                //  Modify the original statement 
                condition = DEPT_ID +" in " + permissionSql;
            } else {
                condition = USER_ID +" = " + userId;
            }

            if (StringUtils.isBlank(condition)){
                return sql;
            }
            Select select = (Select)CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect)select.getSelectBody();
            // Obtain the original SQL Adj. where Condition 
            final Expression expression = plainSelect.getWhere();
            // Add new where Condition 
            final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
            if (expression == null) {
                plainSelect.setWhere(envCondition);
            } else {
                AndExpression andExpression = new AndExpression(expression, envCondition);
                plainSelect.setWhere(andExpression);
            }
            return plainSelect.toString();
        } catch (JSQLParserException e) {
            logger.error(" Analytic primitive SQL And build a new SQL Error: " + e);
            return sql;
        }
    }

Related articles: