数据权限技术验证.
数据权限本质就是根据用户的信息,固定的拼接一些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字段增加一个固定的时间,也就是所有的查询,都限制在一个范围内
依赖
com.alibaba
druid
1.1.10
mysql
mysql-connector-java
5.1.37
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 list = new ArrayList();
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 privList = new ArrayList();
//在SQL上拼接数据权限
public String addPrivilege(final String sql, Map varMap) {
if (varMap == null) {
varMap = UserPrivSession.get();
}
// SQLParserUtils.createSQLStatementParser可以将sql装载到Parser里面
SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, JdbcUtils.MYSQL);
// parseStatementList的返回值SQLStatement本身就是druid里面的语法树对象
List 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 fieldList, final Map 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 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 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 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 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 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 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 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
运行 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/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/29254281/viewspace-2212990/