Shiro介紹(八):數據權限的研究@RequiresData


繼續,上次我說權限可以分為兩大類:操作權限與數據權限。見此篇博文:http://blog.csdn.net/sharetop/article/details/50281669

Shiro幫我們實現的大多為操作權限,那么今天我想分享一個數據權限的方案,主要采用的仍是注解+切面攔截。

思路大概是這樣的:

  1. 在controller的方法參數,約定包含一個Map類型的parameters
  2. 通過注解聲明一下當前用戶的某個成員屬性值需要被插入到這個parameters中,並且聲明對應的字段名稱
  3. 在方法體中,就可以將parameters中所有成員拿出來生成SQL,實現數據的篩選。

比如,我們需要根據當前登錄用戶的名稱realName,篩選出saleName為當前用戶名稱的銷售數據,又或者,根據當前登錄用戶的groupNames[0]為北京,篩選出所有數據字段province為北京的計費數據。

首先,我們定義注解 RequiresData,代碼如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresData {

String[] props() default "";

String[] fields() default "";

}

兩個屬性都是字符串數組,所以我們要使用時可以是這樣的:

@RequiresData(props={"realName","groupNames[0]"},fields={"saleName","province"})

然后,我們需要修改AuthorizationAttributeSourceAdvisor,同樣添加對新注解的支持。

private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {

RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class,
RequiresAction.class,RequiresData.class
};

也同樣修改我們繼承的AopAllianceAnnotationsAuthorizingMethodInterceptor,添加新的DataAnnotationMethodInterceptor支持。

public AopAllianceAnnotationsAuthorizingMethodInterceptor(){
super();

this.methodInterceptors.add(new ActionAnnotationMethodInterceptor(new SpringAnnotationResolver()));
this.methodInterceptors.add(new DataAnnotationMethodInterceptor(new SpringAnnotationResolver()));

}

這次我們的DataAnnotationHandler是不需要做任何事情的,因為我們不是做權限驗證,而是要修改方法參數。

所以,我們需要先定義一個接口。

public interface DataParameterRequest {
Map<String,String> getParameters();
}

保證我們在方法參數實現此接口,比如我們的參數是ConditionRequest,那么代碼如下:

public class ConditionRequest implements DataParameterRequest{

public String author;

private Map<String,String> params = new HashMap<String,String>();

@Override
public Map<String, String> getParameters() {
// TODO Auto-generated method stub
return params;
}

public void setParameters(Map<String,String> p){
this.params=p;
}

}

然后在Controller的方法是這樣的:

@RequiresData(props={"realName","groupNames[1]"},fields={"saleName","province"})
@RequestMapping(value = "/data", method = RequestMethod.POST, headers = {
"Content-Type=application/json;charset=utf-8", "Accept=application/json" })
public @ResponseBody Map<String,String> showData(@RequestBody ConditionRequest req){
//這里需要根據req.getParameters()得到的Map去構造出SQL查詢條件,篩選出數據
}

下面討論一下如何利用AOP修改方法參數,主要是兩個地方要修改,一是AopAllianceAnnotationsAuthorizingMethodInterceptor中需要重載invoke,讓它能保證在遇到RequiresData時能調用DataAnnotationMethodInterceptor的invoke。

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
assertAuthorized(mi);

Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
if (aamis != null && !aamis.isEmpty()) {
for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
if (aami.supports(mi)){

//針對DataAnnotationMethodInterceptor,有特殊的處理
if(aami instanceof DataAnnotationMethodInterceptor) {
return ((DataAnnotationMethodInterceptor)aami).invoke(mi);
}

}
}
}

//其它情況均使用系統缺省
return super.invoke(mi);
}

再者需要修改DataAnnotationMethodInterceptor,同樣重載invoke方法,這是主要功能邏輯所在位置。

@SuppressWarnings("unchecked")
private Map<String,String> _addParameters(String[] props,String[] fields,Class<?> clz,Object principal) throws Exception {
Map<String,String> params = new HashMap<String,String>();

for(int i=0;i<props.length;i++){
String prop = props[i];
String field = fields[i];
int index = -1;

String[] strs = StringUtils.tokenizeToStringArray(prop, "[]");
if(strs.length>1){
prop = strs[0];
index = Integer.valueOf(strs[1]);
}

String propValue = "";
Field p = clz.getDeclaredField(prop);
if(Modifier.PRIVATE==p.getModifiers()){
String m_getter_name = "get"+StringUtils.uppercaseFirstChar(prop);
Method method = clz.getDeclaredMethod(m_getter_name);
Object ret = method.invoke(principal);
if(index>-1 && ret instanceof List<?>){
propValue = ((List<Object>)ret).get(index).toString();
}
else
propValue = ret.toString();
}
else{
Object ret = p.get(principal);
if(index>-1 && ret instanceof List<?>){
propValue = ((List<Object>)ret).get(index).toString();
}
else {
propValue = ret.toString();
}
}
System.out.println(propValue);

params.put(field, propValue);

}
return params;
}

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// TODO Auto-generated method stub
assertAuthorized(methodInvocation);

Object obj = methodInvocation.getThis();
Object[] args = methodInvocation.getArguments();

RequiresData an = (RequiresData)this.getAnnotation(methodInvocation);
Object principal = this.getSubject().getPrincipal();
Class<?> clz = principal.getClass();

String[] props = an.props();
String[] fields = an.fields();

for(Object o : args){
if( o instanceof DataParameterRequest ){
Map<String,String> m = (Map<String,String>)((DataParameterRequest)o).getParameters();
if(m!=null){
Map<String,String> mm = this._addParameters(props, fields, clz, principal);
m.putAll(mm);
}
}
}

return methodInvocation.getMethod().invoke(obj, args);

}

大概解釋一下,在invoke中,當前登錄的用戶是這個Object principal = this.getSubject().getPrincipal(); ,然后取出方法參數,是個數組,Object[] args = methodInvocation.getArguments(); 找到它里面那個DataParameterRequest類型的參數,根據注解聲明的屬性方法與Map中的字段對應關系,添加到args中的那個DataParameterRequest中的parameters里面去。就可以了。
注意,最后需要將args傳入methodInvocation.getMethod().invoke(obj, args);

代碼同樣放在GitHub里了,地址如下:https://github.com/sharetop/shiro-extension/tree/develop

有問題歡迎交流。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com