import java.lang.reflect.Method; import org.apache.log4j.Logger; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.web.context.ContextLoader; /** * * We construct data security layer,Generally speaking: * When the wms start, we detect the security metadata and put it into WMSSecurityContext * A request arrive,if there is an Annotation EnbableDataSewcurity upon the method,we register(mistake) method info into WMSSecurityContext * Before data access,we will check if the "data access session" need in an "security environment". * If yes,we alternative sql.then post to db. * please see detail information about alternative sql in class DataPermissionSqlUtil * * */ public class DataPermissionBeforeAdvice implements MethodBeforeAdvice { //logger for class DataPermissionBeforeAdvice private static final Logger logger = Logger.getLogger(DataPermissionBeforeAdvice.class); @Override public void before(Method method, Object[] args, Object target) throws Throwable { boolean shouldExecuteDataSecurity = checkIfNeedExecuteDataSecurity(); if (shouldExecuteDataSecurity) { executeDataSecurity(method, args, target, shouldExecuteDataSecurity); } } /** * check if the "session" need an security context. * */ private boolean checkIfNeedExecuteDataSecurity() { boolean shouldExecuteDataSecurity = false ; String dataSecurityMethodSignature = WMSSecurityContext.getDataSecurityMethodSignature().get(); if (dataSecurityMethodSignature != null) { // 确定调用方法拥有 @EnableDataSecurity注解标识 // web.xml文件中已经启用数据权限 String enableDataSecurity = ContextLoader.getCurrentWebApplicationContext().getServletContext().getInitParameter("EnableDataSecurity"); if (enableDataSecurity != null && "true".equals(enableDataSecurity)) { shouldExecuteDataSecurity = true; } } return shouldExecuteDataSecurity; } /** * retrieve security metadata ,and alternative sql(s). * */ @SuppressWarnings("rawtypes") private void executeDataSecurity(Method method, Object[] args, Object target, boolean shouldExecuteDataSecurity) throws Throwable { if (target instanceof JdbcOperations) { if (logger.isDebugEnabled()) { logger.debug("exeute data security begin."); } // do alternative sql operation if (shouldExecuteDataSecurity) { Class[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Class paramterType = parameterTypes[i]; if ("String".equals(paramterType.getSimpleName())) { String sql = (String) args[i]; String sqlByDataPermission = DataPermissionSqlUtil.alternateSqlByDataPermission(sql); if (logger.isDebugEnabled()) { logger.debug("sql after data permission: " + sqlByDataPermission); } args[i] = sqlByDataPermission; } else if ("String[]".equals(paramterType.getSimpleName())) { String[] sqls = (String[]) args[i]; for (int j = 0; j < sqls.length; j++) { String sql = sqls[j]; String sqlByDataPermission = DataPermissionSqlUtil.alternateSqlByDataPermission(sql); sqls[j] = sqlByDataPermission; } args[i] = sqls; } } } if (logger.isDebugEnabled()) { logger.debug("exeute data security end."); } } } }
import java.io.StringReader; import java.util.List; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.replace.Replace; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.util.TablesNamesFinder; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; /** * Handy class for alternative SQL with data authority and user context */ public class DataPermissionSqlUtil { //logger for class DataPermissionSqlUtil private static final Logger logger = Logger.getLogger(DataPermissionSqlUtil.class); private static CCJSqlParserManager pm = new CCJSqlParserManager(); /** * calculate subquery by table name * */ public static String alternateSqlByDataPermission(String sql) throws Exception { if (logger.isDebugEnabled()) { logger.debug("alternateSqlByDataPermission begin..."); } List<String> tableNames = getTableNames(sql); // if sql is not SELECT type // return sql directly if (null == tableNames) { return sql; } for (String tablename : tableNames) { // calculate alternative SQL String subquery = getSubqueryByTableName(tablename); if (logger.isDebugEnabled()) { logger.debug("table name is : " + tablename + " subquery is : " + subquery); } // replace table name to subquery sql = sql.replaceAll(tablename, subquery); // 只替换第一个表名(主表),关联表名 不替换 break; } return sql; } /** * detect table names from given table * ATTENTION : WE WILL SKIP SCALAR SUBQUERY IN PROJECTION CLAUSE * */ private static List<String> getTableNames(String sql) throws Exception { List<String> tablenames = null; TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); Statement statement = pm.parse(new StringReader(sql)); if (statement instanceof Select) { tablenames = tablesNamesFinder.getTableList((Select) statement); } else if (statement instanceof Update) { return null; } else if (statement instanceof Delete) { return null; } else if (statement instanceof Replace) { return null; } else if (statement instanceof Insert) { return null; } return tablenames; } private static String getSubqueryByTableName(String tableName) throws Exception { String subquery = null; String relatedFromClause = processFromConstriant(tableName); // 拼接关联表查询字段 控制权限 String experiyWhereClause = processExperiyConstriant(tableName); // 拼接Status字段控制启用 禁用,指定状态字段 String domainWhereClause = processDomainConstriant(tableName); // 根据员工登录 控制只能看到当前登录机构的数据,指定机构字段 String dataPermissionWhereClause = processDataPermissionConstriant(tableName); // 数据权限控制 if(StringUtils.isEmpty(experiyWhereClause) && StringUtils.isEmpty(domainWhereClause) && StringUtils.isEmpty(dataPermissionWhereClause)) { return tableName; } else { StringBuilder querySb = new StringBuilder() .append(" ( ").append(SqlConstants.SQL_SELECT).append(" ").append(DataSecurityHelper.getMainTableAlias(tableName)).append(" * ") .append(SqlConstants.SQL_FROM).append(" ").append(tableName).append(" "); if(StringUtils.isNotBlank(relatedFromClause)) { querySb.append(relatedFromClause).append(" "); } querySb.append(SqlConstants.SQL_WHERE).append(" "); if(StringUtils.isNotBlank(experiyWhereClause)) { querySb.append(experiyWhereClause).append(" ").append(SqlConstants.SQL_AND); } if(StringUtils.isNotBlank(domainWhereClause)) { querySb.append(domainWhereClause).append(" ").append(SqlConstants.SQL_AND); } if(StringUtils.isNotBlank(dataPermissionWhereClause)) { querySb.append(dataPermissionWhereClause).append(" ").append(SqlConstants.SQL_AND); } querySb = StringUtil.deleteLastSequence(querySb,SqlConstants.SQL_AND); querySb.append(" ) "); subquery = querySb.toString(); } return subquery; } /** * filter related from table column data * lifeng * */ private static String processFromConstriant(String tableName) { String relatedFromClause = DataSecurityHelper.buildRelatedConstriantSql(tableName); return relatedFromClause; } /** * filter expiry data * */ private static String processExperiyConstriant(String tableName) { String experiyWhereClause = DataSecurityHelper.buildExpiryConstriantSql(tableName); return experiyWhereClause; } /** * filter domain data * */ private static String processDomainConstriant(String tableName) { String domainWhereClause = DataSecurityHelper.buildDomainConstriantSql(tableName); return domainWhereClause; } /** * filter permission data * */ private static String processDataPermissionConstriant(String tableName) { String dataPermissionWhereClause = DataSecurityHelper.buildDataAuthorityConstriantSql(tableName); return dataPermissionWhereClause; } }
applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd"> <mvc:annotation-driven /> <context:annotation-config /> <context:component-scan base-package="com.yundaex.wms" /> <!-- Base package to scan the mongo repositories, where we create de DAOS to access data and domain objects --> <mongo:repositories base-package="com.yundaex.wms.security.mongo" /> <bean id="dataPermissionBeforeAdvice" class="com.yundaex.common.security.advice.DataPermissionBeforeAdvice" /> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(public * org.springframework.jdbc.core.JdbcTemplate.*(..)) and !execution(public * org.springframework.jdbc.core.JdbcTemplate.queryForList(*))" id="jdbcTemplatePointcut"/> <aop:advisor advice-ref="dataPermissionBeforeAdvice" pointcut-ref="jdbcTemplatePointcut"/> </aop:config> <bean id="mongoRoleDao" class="com.yundaex.wms.security.mongo.impl.MongoRoleDaoImpl"> </bean> <bean id="mongoFunctionAuthorityDao" class="com.yundaex.wms.security.mongo.impl.MongoFunctionAuthorityDaoImpl"> </bean> <bean id="mongoUserDao" class="com.yundaex.wms.security.mongo.impl.MongoUserDaoImpl"></bean> </beans>
web.xml
<!-- Enable data security --> <context-param> <param-name>EnableDataSecurity</param-name> <param-value>true</param-value> </context-param>