1. 程式人生 > >基於mybatis攔截器實現資料許可權

基於mybatis攔截器實現資料許可權

資料許可權是很多系統常見的功能,實現的方式也是很多的,最近在做專案的時候,自己基於mybatis攔截器做了一個數據許可權的功能。

**功能設計

a)  需要做資料許可權功能的表加上一個許可權id欄位

許可權id可以不僅僅是組織,還可以是其他自定義的欄位,用來做資料許可權,如:

主鍵Id

欄位1

欄位2

欄位3

許可權id

1

xxx

xxx

xxx

A001

2

xxx

xxx

xxx

A002

3

xx

xxx

xxx

A003

 

b)  1.分配許可權id給資料角色;2.分配角色給員工, 這樣就實現了許可權的分配。

c)   員工-崗位-組織的關係如下,可以加一個開關,可以選擇是否同時選用預設組織的許可權資料。      

d)  取到步驟b)和c)的許可權id列表後,組裝到sql裡面進行如下查詢:

select   *  from  table  where  許可權id   in  (‘A001’,’A002’,’A003’)

e) 為了減少程式碼的耦合性,採取註解的方式,在mapper.java的查詢方法上面加註解:

**前臺頁面參考

測試表:

CREATE TABLE `lcp_test_auth` (
  `test_auth_id` bigint(20) NOT NULL COMMENT '主鍵ID',
  `attr1` varchar(64) NOT NULL COMMENT '欄位1',
  `org_id` bigint(20) NOT NULL COMMENT '組織id',
  `warehouse_id` bigint(20) NOT NULL COMMENT '倉庫id',
  `created_by` bigint(20) DEFAULT NULL,
  `creation_date` datetime DEFAULT CURRENT_TIMESTAMP,
  `last_updated_by` bigint(20) DEFAULT NULL,
  `last_update_date` datetime DEFAULT CURRENT_TIMESTAMP,
  `object_version_number` bigint(20) DEFAULT '1',
  `request_id` bigint(20) DEFAULT NULL,
  `program_id` bigint(20) DEFAULT NULL,
  `last_update_login` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`test_auth_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='資料許可權測試表';

 

 

**後臺功能實現

a) mybatis攔截器的xml配置,先在xml中配置好plugins,也就是自定義攔截器的類

 

b) 編寫自己的攔截器類,我在攔截器裡面需要做的事情是:

1.得到將要執行的原來的sql;

2.從request裡面得到當前登入的使用者的資訊;

3.根據當前使用者資訊查詢其擁有的許可權資料;

4.根據許可權資料拼接sql並修改原sql;

5.sql執行,完。

關於mybatis攔截器的內容可以參考:https://blog.csdn.net/xiao_jun_0820/article/details/70308253

下面是我的mybatis攔截類,其中DataAuth 是我的註解類,AuthSqlSource是我的sqlSource類,僅作為參考(直接copy肯定是跑不起來的,嘿嘿)

package com.fsl.lcp.interceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fsl.lcp.annotation.DataAuth;
import com.fsl.lcp.auth.dto.AuthorityResource;
import com.fsl.lcp.auth.service.IAuthorityResourceService;
import com.fsl.lcp.auth.service.IDataRoleResourceService;
import com.fsl.lcp.auth.sqlsource.AuthSqlSource;
import com.hand.hap.core.components.ApplicationContextHelper;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

/**
 * mybatis資料許可權攔截器
 * @author tanqian
 * 2018年11月29日
 */
@Intercepts({@Signature(method = "query", type = Executor.class,
	args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class })})
public class AuthLcpInterceptor implements Interceptor {
	
	private static final Logger log = LoggerFactory.getLogger(AuthLcpInterceptor.class);
	
	private ApplicationContext beanFactory;
	
	private IAuthorityResourceService iAuthorityResourceService;
	
	private IDataRoleResourceService iDataRoleResourceService;
	
	@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 {
		//初始化bean
		this.loadService();
		
		MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
		// 獲取方法上的資料許可權註解,如果沒有註解,則直接通過
		DataAuth dataAuth = getPermissionByDelegate(mappedStatement);
		if (dataAuth == null) {
			return invocation.proceed();
		}
		// 獲取request資訊,得到當前登入使用者資訊
		RequestAttributes req = RequestContextHolder.getRequestAttributes();
		if (req == null) {
			return invocation.proceed();
		}
		//處理request
		HttpServletRequest request = ((ServletRequestAttributes) req).getRequest();
		//如果request裡面的使用者/員工資訊為空,則直接拋異常
		if(request.getSession().getAttribute("employeeId") == null || request.getSession().getAttribute("userId") == null){
			throw new RuntimeException("此查詢為許可權資料,必須是擁有員工和使用者屬性的賬號登入才可以查詢!");
		}
		//employeeId
		Long employeeId = Long.valueOf(request.getSession().getAttribute("employeeId").toString());
		//userId
		Long userId = Long.valueOf(request.getSession().getAttribute("userId").toString());
		//處理組織許可權資料,並返回組織許可權sql
		String orgAuthSql = this.dealOrgAuth(dataAuth,employeeId);
		//處理資料許可權資料,並返回資料許可權sql
		String authSql = this.dealResourceSql(dataAuth,userId);
	
		//如果兩種sql都為空,那直接返回
		if("".equals(orgAuthSql.trim()) && "".equals(authSql.trim())){
			return invocation.proceed();
		}
		log.info("待拼接sql:組織許可權sql:"+orgAuthSql + ",資料許可權sql:" + authSql);
		//原sql
		String sql = mappedStatement.getBoundSql(invocation.getArgs()[1]).getSql();
		//處理sql拼接
		this.permissionSql(sql,orgAuthSql,authSql,invocation);
		
		return invocation.proceed();

	}

	/**
	 * 處理資料許可權資料,並返回資料許可權sql
	 * @param dataAuth
	 * @param userId
	 * @return
	 */
	private String dealResourceSql(DataAuth dataAuth, Long userId) {
		String authSql = "";
		//資料許可權註解
		String resourceCode = dataAuth.resourceCode();
		if(!resourceCode.equals("")){
			List<AuthorityResource> resourceList = iAuthorityResourceService.selectAuthSqlByUserIdAndResourceCode(userId, resourceCode);
			if(resourceList.size() > 0){
				authSql = resourceList.get(0).getAuthorityResourceSql();
			}
		}
		return authSql;
	}

	/**
	 * 處理組織許可權資料,並返回組織許可權sql
	 * @param dataAuth
	 * @param employeeId 
	 * @return
	 */
	private String dealOrgAuth(DataAuth dataAuth, Long employeeId) {
		String orgAuthSql = "";
		//組織許可權註解
		String orgAuth = dataAuth.orgAuth();
		//組織欄位註解
		String authOrgId = dataAuth.authOrgId();
		if(orgAuth.equals("Y")){
			String orgIdList = iDataRoleResourceService.getDefaultChildUnitList(employeeId);
			orgAuthSql = orgIdList == null ? "" : authOrgId + " in (" + orgIdList + ")";
		}
		return orgAuthSql;
	}

	/**
	 * 獲取資料許可權註解資訊
	 * 
	 * @param mappedStatement
	 * @return
	 */
	private DataAuth getPermissionByDelegate(MappedStatement mappedStatement) {
		DataAuth dataAuth = null;
		try {
			String id = mappedStatement.getId();
			String className = id.substring(0, id.lastIndexOf("."));
			String methodName = id.substring(id.lastIndexOf(".") + 1, id.length());
			final Class<?> cls = Class.forName(className);
			final Method[] method = cls.getMethods();
			for (Method me : method) {
				if (me.getName().equals(methodName) && me.isAnnotationPresent(DataAuth.class)) {
					dataAuth = me.getAnnotation(DataAuth.class);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataAuth;
	}

	/**
	 * 許可權sql包裝
	 * @param sql 原sql
	 * @param authSql 資料許可權sql 
	 * @param orgAuthSql 組織許可權sql
	 * @param invocation
	 */
	private void permissionSql(String sql,String orgAuthSql, String authSql, Invocation invocation) {
		final Object[] args = invocation.getArgs();
		MappedStatement statement = (MappedStatement) args[0];
		Object parameterObject = args[1];
		BoundSql boundSql = statement.getBoundSql(parameterObject);
		MappedStatement newStatement = newMappedStatement(statement, new AuthSqlSource(boundSql));
		MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		//sql拼接
		if("".equals(authSql.trim()) && (!"".equals(orgAuthSql.trim()))){
			if(sql.toUpperCase().contains("WHERE")){
				sql = sql + " AND " + orgAuthSql;
			}else{
				sql = sql + " WHERE " + orgAuthSql;
			}
		}else{
			if("".equals(orgAuthSql.trim())){
				sql = sql + " " + authSql;
			}else{
				sql = sql + " "+ authSql + " AND " + orgAuthSql;
			}
			
		}
		//sql替換
		msObject.setValue("sqlSource.boundSql.sql", sql);
		args[0] = newStatement;
	}
	
	/**
	 * MappedStatement包裝
	 * @param ms
	 * @param newSqlSource
	 * @return
	 */
	private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
				ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuilder keyProperties = new StringBuilder();
			for (String keyProperty : ms.getKeyProperties()) {
				keyProperties.append(keyProperty).append(",");
			}
			keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
			builder.keyProperty(keyProperties.toString());
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.resultSetType(ms.getResultSetType());
		builder.cache(ms.getCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());
		builder.useCache(ms.isUseCache());

		return builder.build();
	}
	
	
    /**
     * 載入注入的bean
     */
    private void loadService() {
        if (null == beanFactory) {
            beanFactory = ApplicationContextHelper.getApplicationContext();
            if(null == beanFactory){
            	return;
            }
        }
        if (iAuthorityResourceService == null) {
        	iAuthorityResourceService = beanFactory.getBean(IAuthorityResourceService.class);
        }
        if (iDataRoleResourceService == null) {
        	iDataRoleResourceService = beanFactory.getBean(IDataRoleResourceService.class);
        }
        
    }
	
	 
}

DataAuth註解類:

package com.fsl.lcp.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 資料許可權實現的註解
 * @author tanqian
 * @date 2018年11月21日
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataAuth {
	/**
	 * 1.許可權資源code,許可權資源定義頁面設定的,指明這次查詢需要使用哪條資源sql </br>
	 * 2.如果定義了資源sql,卻沒有分配給當前登陸的使用者,則不會返回任何資料</br>
	 * 3.輸入不存在的資源code,也不會返回任何資料</br>
	 * 4.定義的sql寫的有問題的,會產生不可預知的錯誤
	 * @return
	 */
	String resourceCode() default "";
	/**
	 * orgAuth </br>
	 * 組織許可權,Y代表開啟,N和不寫代表不開啟
	 * @return
	 */
	String orgAuth() default "";
	/**
	 * authOrgId </br>
	 * 許可權組織欄位,orgAuth=Y的時候必須寫,否則無法獲取哪個表的哪個欄位是組織欄位</br>
	 * 值為:"許可權業務表的別名.組織欄位名"</br>
	 * 如:原sql為"select * from 許可權業務表  auth, 業務表A ya where auth.org_id=123",</br>
	 * 則該註解應填值為:"auth.org_id"
	 * @return
	 */
	String authOrgId() default "";
}

AuthSqlSource類,我是因為基於我們自己的hap框架,必須繼承我們框架的PageSqlSource類,自己寫的話,可以直接實現SqlSource介面 :

package com.fsl.lcp.auth.sqlsource;

import org.apache.ibatis.mapping.BoundSql;

import com.github.pagehelper.sqlsource.PageSqlSource;
/**
 * 攔截器需要的SqlSource,必須繼承自hap修改後的PageSqlSource
 * @author tanqian
 * 2018年11月29日
 */
public 	class AuthSqlSource extends PageSqlSource {
	private BoundSql boundSql;

	public AuthSqlSource(BoundSql boundSql) {
		this.boundSql = boundSql;
	}

	@Override
	public BoundSql getBoundSql(Object parameterObject) {
		return boundSql;
	}

	@Override
	protected BoundSql getDefaultBoundSql(Object parameterObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected BoundSql getCountBoundSql(Object parameterObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected BoundSql getPageBoundSql(Object parameterObject) {
		// TODO Auto-generated method stub
		return null;
	}
}

c)現在就可以進行測試了,寫個最簡單的controller-service-mapper來測試就行,我僅貼出mapper.java的程式碼了:

package com.fsl.lcp.test.mapper;

import com.hand.hap.mybatis.common.Mapper;

import java.util.List;

import com.fsl.lcp.annotation.DataAuth;
import com.fsl.lcp.test.dto.TestAuth;

public interface TestAuthMapper extends Mapper<TestAuth>{


	@DataAuth(orgAuth="Y",authOrgId="lta.org_id")
	List<TestAuth> testOrgAuth();
	
	@DataAuth(resourceCode="test_auth")
	List<TestAuth> testDefineAuth();

	@DataAuth(resourceCode="test_auth",orgAuth="Y",authOrgId="lta.org_id")
	List<TestAuth> testOrgAndDefineAuth();
	


}