ITPub博客

首页 > 数据库 > MySQL > 数据权限技术验证

数据权限技术验证

原创 MySQL 作者:壹頁書 时间:2018-08-28 11:23:45 0 删除 编辑

数据权限技术验证.

数据权限本质就是根据用户的信息,固定的拼接一些SQL

基于阿里开源的Druid的SQL Parse模块 ,做了一些动态拼接SQL的技术验证.


参考:

http://mrchenatu.com/2017/01/19/druid-sql-parse/


数据权限的配置在数据库中建表

CREATE TABLE `dataprivilegeconfig` (
  `Id` bigint(20) NOT NULL AUTO_INCREMENT,
  `Project` varchar(128) NOT NULL COMMENT '项目名称',
  `TableName` varchar(128) NOT NULL COMMENT '表名称',
  `ColName` varchar(128) NOT NULL COMMENT '列名称',
  `Oper` varchar(8) NOT NULL COMMENT '操作.仅限于[>,<,=,>=,<=,!=,in,not in]',
  `Var` varchar(32) NOT NULL COMMENT '程序中定义的变量名称',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  COMMENT='数据权限配置表'



以上的配置,

id=1 的配置 希望在所有的nums表的id字段  增加 小于等于的一个权限.但是具体的值,需要程序动态的传入.

id=2 的配置 在kylin_query_log的表的cube_id 增加一个 in的列表,这个列表的值需要动态传入

id=3 的配置 在kylin_query_log的表的query_time字段增加一个固定的时间,也就是所有的查询,都限制在一个范围内


依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>



SQLDataPrivilege.java 核心解析类

package org.datapriv.datapriv;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLQueryExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserUtils;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.druid.util.JdbcUtils;
/**
 * Hello world!
 *
 */
public class SQLDataPrivilege {
	public static void main(String[] args) {
		UserPrivSession.set("nums.id", "3");
		List<String> list = new ArrayList<String>();
		list.add("0342e276-c046-40f0-be4a-1a04092bc7dd");
		list.add("090493b4-aa59-4680-a707-907cb139ebd1");
		list.add("098806d5-c07a-46c9-8901-e50045466455");
		list.add("0bbfa1ed-9ec2-4e9e-9dfd-181945cb5eed");
		list.add("0bd9528a-447b-4dc3-950a-4a45e066f22d");
		UserPrivSession.set("kylin_query_log.cube_id", list, true);
		SQLDataPrivilege a = new SQLDataPrivilege();
		String sql = "select t4.dt,ifnull(t3.value,0) value from\r\n" + 
				"        (\r\n" + 
				"            select '2018-07-28'+interval (id-1) day dt from nums where id<=datediff('2018-08-03','2018-07-28')+1\r\n" + 
				"        ) t4\r\n" + 
				"        left join\r\n" + 
				"        (\r\n" + 
				"            select * from (\r\n" + 
				"                select date_format(query_time,'%Y-%m-%d') dt,cast(sum(result) as SIGNED) value from kylin_result_cache_by_cubeid\r\n" + 
				"                where\r\n" + 
				"                module='query_total_by_cubeid' and\r\n" + 
				"                cube_id in (select id from kylin_cube where is_delete=0) and\r\n" + 
				"                 \r\n" + 
				"        query_time< date_format('2018-08-03'+interval 1 day,'%Y-%m-%d 00:00:00') and  query_time>= date_format('2018-07-28','%Y-%m-%d 00:00:00')\r\n" + 
				"         \r\n" + 
				"     \r\n" + 
				"                group by date_format(query_time,'%Y-%m-%d')\r\n" + 
				"            ) t1 union all\r\n" + 
				"\r\n" + 
				"                select date_format(query_time,'%Y-%m-%d') dt,count(*) value from kylin_query_log\r\n" + 
				"                where\r\n" + 
				"                cube_id in (select id from kylin_cube where is_delete=0) and\r\n" + 
				"                 \r\n" + 
				"        query_time>=date_format(now() ,'%Y-%m-%d 00:00:00')\r\n" + 
				"        and query_time<date_format(now()+interval 1 day ,'%Y-%m-%d 00:00:00')\r\n" + 
				"         \r\n" + 
				"     \r\n" + 
				"                group by date_format(query_time,'%Y-%m-%d')\r\n" + 
				"\r\n" + 
				"        ) t3\r\n" + 
				"        on(t3.dt=t4.dt)\r\n" + 
				"        where t4.dt between '2018-07-28' and '2018-08-03'\r\n" + 
				"        order by t4.dt\r\n" + 
				"";
		a.addPrivilege(sql, null);
	}
	
	
	//单例.该对象用于给已经存在的SQL增加数据权限
	private static SQLDataPrivilege INSTANCE = new SQLDataPrivilege();
	public static SQLDataPrivilege getInstance() {
		return INSTANCE;
	}
	//从数据库中获取配置信息
	private SQLDataPrivilege() {
		try {
			Class.forName("com.mysql.jdbc.Driver");
			Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "laohuali@58");
			PreparedStatement ps = con.prepareStatement("select * from DataPrivilegeConfig where Project='测试'");
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				Privilege p = new Privilege();
				p.setTableName(rs.getString("TableName"));
				p.setColName(rs.getString("ColName"));
				p.setOper(rs.getString("Oper"));
				p.setValue(rs.getString("Var"));
				privList.add(p);
			}
			rs.close();
			ps.close();
			con.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	//保存本项目的数据权限配置信息
	private List<Privilege> privList = new ArrayList<Privilege>();
	//在SQL上拼接数据权限
	public String addPrivilege(final String sql, Map<String, String> varMap) {
		if (varMap == null) {
			varMap = UserPrivSession.get();
		}
		// SQLParserUtils.createSQLStatementParser可以将sql装载到Parser里面
		SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, JdbcUtils.MYSQL);
		// parseStatementList的返回值SQLStatement本身就是druid里面的语法树对象
		List<SQLStatement> stmtList = parser.parseStatementList();
		SQLStatement stmt = stmtList.get(0);
		//如果不是查询,则返回
		if (!(stmt instanceof SQLSelectStatement)) {
			return sql;
		}
		SQLSelectStatement selectStmt = (SQLSelectStatement) stmt;
		// 拿到SQLSelect 通过在这里打断点看对象我们可以看出这是一个树的结构
		SQLSelect sqlselect = selectStmt.getSelect();
		SQLSelectQueryBlock query = (SQLSelectQueryBlock) sqlselect.getQuery();
		 
		parseSubQuery(query.getSelectList(), varMap);
		parseTable(query, varMap);
		System.out.println(sqlselect.toString());
		return sqlselect.toString();
	}
	//给子查询增加数据权限
	private void parseSubQuery(final List<SQLSelectItem> fieldList, final Map<String, String> varMap) {
		for (SQLSelectItem item : fieldList) {
			if (item.getExpr() instanceof SQLQueryExpr) {
				SQLQueryExpr expr = (SQLQueryExpr) item.getExpr();
				parseTable(expr.getSubQuery().getQueryBlock(), varMap);
			}
		}
	}
	//递归处理嵌套表
	private void parseTable(final SQLSelectQueryBlock query, final Map<String, String> varMap) {
		if (query == null) {
			return;
		}
		SQLTableSource tableSource = query.getFrom();
		if (tableSource instanceof SQLExprTableSource) {
			//如果是普通的表,则在where中增加数据权限
			SQLExprTableSource table = ((SQLExprTableSource) tableSource);
			String tableName = table.getName().getSimpleName();
			String aliasName = table.getAlias();
			SQLExpr sqlExpr = createSQLExpr(tableName, aliasName, varMap);
			createWhereSQLExpr(query, varMap, sqlExpr);
		} else if (tableSource instanceof SQLSubqueryTableSource) {
			//如果是嵌套表,则递归到内层
			SQLSubqueryTableSource table = ((SQLSubqueryTableSource) tableSource);
			parseTable(table.getSelect().getQueryBlock(), varMap);
		} else if (tableSource instanceof SQLJoinTableSource) {
			//如果是两个表关联.则在on条件中增加数据权限。并且在左右表中分别判断是否是union all的情况
			SQLJoinTableSource table = ((SQLJoinTableSource) tableSource);
			SQLTableSource left = table.getLeft();
			SQLTableSource right = table.getRight();
			SQLExpr onExpr = table.getCondition();
			if (left instanceof SQLSubqueryTableSource) {
				SQLSubqueryTableSource leftTable = ((SQLSubqueryTableSource) left);
				parseUnion(leftTable.getSelect().getQuery(), varMap);
				parseTable(leftTable.getSelect().getQueryBlock(), varMap);
			} else if (left instanceof SQLExprTableSource) {
				SQLExprTableSource joinTable = ((SQLExprTableSource) left);
				onExpr = createOnExpr(joinTable, onExpr, varMap);
			}
			if (right instanceof SQLSubqueryTableSource) {
				SQLSubqueryTableSource rightTable = ((SQLSubqueryTableSource) right);
				parseUnion(rightTable.getSelect().getQuery(), varMap);
				parseTable(rightTable.getSelect().getQueryBlock(), varMap);
			} else if (right instanceof SQLExprTableSource) {
				SQLExprTableSource joinTable = ((SQLExprTableSource) right);
				onExpr = createOnExpr(joinTable, onExpr, varMap);
			}
			table.setCondition(onExpr);
		}
	}
	//如果是union all的情况,则通过递归进入内层
	private boolean parseUnion(final SQLSelectQuery query, final Map<String, String> varMap) {
		if (query instanceof SQLUnionQuery) {
			SQLUnionQuery unionQuery = (SQLUnionQuery) query;
			if (unionQuery.getLeft() instanceof SQLUnionQuery) {
				parseUnion(unionQuery.getLeft(), varMap);
			} else if (unionQuery.getLeft() instanceof SQLSelectQueryBlock) {
				SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) unionQuery.getLeft();
				parseTable(queryBlock, varMap);
			}
			if (unionQuery.getRight() instanceof SQLUnionQuery) {
				parseUnion(unionQuery.getRight(), varMap);
			} else if (unionQuery.getRight() instanceof SQLSelectQueryBlock) {
				SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) unionQuery.getRight();
				parseTable(queryBlock, varMap);
			}
			return true;
		}
		return false;
	}
	//在连接的on条件中拼接权限
	private SQLExpr createOnExpr(SQLExprTableSource joinTable, SQLExpr onExpr, final Map<String, String> varMap) {
		String tableName = joinTable.getName().getSimpleName();
		String aliasName = joinTable.getAlias();
		SQLExpr sqlExpr = createSQLExpr(tableName, aliasName, varMap);
		if (sqlExpr != null) {
			SQLBinaryOpExpr newWhereExpr = new SQLBinaryOpExpr(onExpr, SQLBinaryOperator.BooleanAnd, sqlExpr);
			onExpr = newWhereExpr;
		}
		return onExpr;
	}
	//根据配置获取拼接好的权限SQL
	private SQLExpr createSQLExpr(String tableName, String aliasName, final Map<String, String> varMap) {
		StringBuffer constraintsBuffer = new StringBuffer("");
		for (Privilege p : privList) {
			if (tableName.equals(p.getTableName())) {
				constraintsBuffer.append(" and ");
				constraintsBuffer.append(p.toString(aliasName, varMap));
			}
		}
		if ("".equals(constraintsBuffer.toString())) {
			return null;
		}
		SQLExprParser constraintsParser = SQLParserUtils
				.createExprParser(constraintsBuffer.toString().replaceFirst(" and ", ""), JdbcUtils.MYSQL);
		SQLExpr constraintsExpr = constraintsParser.expr();
		return constraintsExpr;
	}
	//拼接where中的权限信息
	private void createWhereSQLExpr(final SQLSelectQueryBlock query, final Map<String, String> varMap,
			SQLExpr sqlExpr) {
		if (sqlExpr == null) {
			return;
		}
		SQLExpr whereExpr = query.getWhere();
		// 修改where表达式
		if (whereExpr == null) {
			query.setWhere(sqlExpr);
		} else {
			SQLBinaryOpExpr newWhereExpr = new SQLBinaryOpExpr(whereExpr, SQLBinaryOperator.BooleanAnd, sqlExpr);
			query.setWhere(newWhereExpr);
		}
	}
}


Privilege 保存配置信息的类

package org.datapriv.datapriv;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Privilege {
	private String tableName;
	private String colName;
	private String oper;
	private String value;
	private Pattern pattern = Pattern.compile("\\{.*\\}");
	public String toString(String aliasName, Map<String, String> varMap) {
		if (aliasName == null || "".equals(aliasName)) {
			aliasName = tableName + ".";
		} else {
			aliasName = aliasName + ".";
		}
		String sqlString = aliasName + colName + " " + oper + " " + value;
		Matcher m = pattern.matcher(value);
		if (m.find()) {
			String var = m.group().replaceAll("(\\{|\\})", "");
			if (varMap.containsKey(var)) {
				sqlString = sqlString.replaceAll("(\\{.*\\})", varMap.get(var));
			} else {
				System.out.println("缺少信息");
				throw new RuntimeException("缺少必要信息");
			}
		}
		return sqlString;
	}
	public String toString(Map<String, String> varMap) {
		return toString(null, varMap);
	}
	@Override
	public String toString() {
		return tableName + "." + colName + " " + oper + " " + value;
	}
	public String getTableName() {
		return tableName;
	}
	public void setTableName(String tableName) {
		this.tableName = tableName;
	}
	public String getColName() {
		return colName;
	}
	public void setColName(String colName) {
		this.colName = colName;
	}
	public String getOper() {
		return oper;
	}
	public void setOper(String oper) {
		this.oper = oper;
	}
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}
}


UserPrivSession 通过ThreadLocal 传参数的类

package org.datapriv.datapriv;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserPrivSession {
	private static ThreadLocal<Map<String,String>> threadLocal=new ThreadLocal<Map<String,String>>();
	
	public static Map<String,String> get(){
		 Map<String,String> map=threadLocal.get();
		 if(map==null) {
			 threadLocal.set(new HashMap<String,String>());
			 map=threadLocal.get();
		 }
		 return map;
	}
	
	public static void set(String key,String value) {
		 Map<String,String> map=get();
		
		 map.put(key, value);
	}
	public static void set(String key,List<String> list,boolean isVarchar) {
		 Map<String,String> map=get();
		 StringBuilder sb=new StringBuilder(128);
		 sb.append(" ( ");
		 for(String str:list) {
			 if(isVarchar) {
				 sb.append("'");
				 sb.append(str);
				 sb.append("',");
			 }else {
				 sb.append(str);
				 sb.append(",");
			 }
		 }
		 sb.deleteCharAt(sb.length()-1);
		 sb.append(" ) ");
		 
		 map.put(key, sb.toString());
	}
 
}


运行 SQLDataPrivilege 的main方法, 查看测试结果.

可以看到已经把权限信息拼接成功.




具体应用场景.

将UserPrivSession 类作为Client给业务方

将SQLDataPrivilege和Privilege 作为独立的jar包


通过javaagent和javassist 在启动时改写MySQL jdbc驱动的com.mysql.jdbc.ConnectionImpl 类

将SQL权限拼接的功能加上

package org.datapriv.agent;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.datapriv.datapriv.UserPrivSession;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
public class JavaAgent {
	public static void premain(String agentArgs, Instrumentation inst) throws NotFoundException, CannotCompileException,
			ClassNotFoundException, UnmodifiableClassException, IOException {
		org.datapriv.datapriv.SQLDataPrivilege.getInstance();
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(JavaAgent.class));
		CtClass cc = pool.get("com.mysql.jdbc.ConnectionImpl");
		CtClass[] paras = new CtClass[1];
		paras[0] = pool.get("java.lang.String");
		CtMethod method = cc.getDeclaredMethod("prepareStatement", paras);
		StringBuffer sb = new StringBuffer();
		sb.append("org.datapriv.datapriv.SQLDataPrivilege p=  org.datapriv.datapriv.SQLDataPrivilege.getInstance();");
		sb.append("sql=p.addPrivilege(sql,null);");
		method.insertBefore(sb.toString());
		ClassDefinition defException = new ClassDefinition(com.mysql.jdbc.ConnectionImpl.class, cc.toBytecode());
		inst.redefineClasses(new ClassDefinition[] { defException });
	}
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserPrivSession.set("deptName", "研发");
		UserPrivSession.set("userid", "123456");
		UserPrivSession.set("id", "10");
	 
		
		
		Class.forName("com.mysql.jdbc.Driver");
		Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "laohuali@58");
		PreparedStatement ps = con.prepareStatement("select * from nums limit 20 ");
		ResultSet rs = ps.executeQuery();
		while (rs.next()) {
			System.out.println(rs.getInt(1));
		}
		rs.close();
		ps.close();
		con.close();
	}
}


启动命令增加javaagent参数,

其中DataPriv.jar

就包括

JavaAgent类

Privilege类

SQLDataPrivilege类 

组成的Runnable jar包

(注意 不包括UserPrivSession 类,这个类需要集成在业务方)

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/29254281/viewspace-2212990/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2013-10-19

  • 博文量
    621
  • 访问量
    5948596