继昨天发布了使用AOP切面和注解消除重复鉴权的代码和文章,又接到了小伙伴提的新需求。
小伙伴A的需求: 存在多个注解时,目前多个鉴权间是 OR 关系(有一个权限位校验成功则成功),能否支持 AND (都鉴权成功才可以)关系呢 ?
小伙伴B的需求:能否根据方法的参数值进行鉴权呢?例如这个方法 get(String domain ), 我希望domain == "taobao.com" 时进行 XXX权限的鉴权。
这当然难不倒我啦,在XX分钟后我就完成了上述需求。 AND、OR的这个我就不描述怎么搞了,直接看代码即可。 今天想顺便介绍下 java 6后新增的的一个神器 ScriptEngine,善用这个东东,可以简化很多的代码。 那么这个ScriptEngine 是什么呢?下面说下个人通俗的理解
ScriptEngine 是java内置的一个解释器,可以在运行时解释执行你的js代码。有了这个神器,以前以拙劣的代码写的类似解释器的代码可以统统抛弃了。仅仅几行代码就搞定,是不是有一种很爽的感觉?
下面就分享下这个加强版的鉴权实现代码,供大家参考:
@Repeatable(AclRightAnnotations.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AclRightAnnotation {
AclRightType value() ;
ResultType result() default ResultType.JSON_EXCEPTION; //action/forward
LogicType logic() default LogicType.OR; //action/forward
String expr() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AclRightAnnotations {
AclRightAnnotation[] value() ;
}
public enum LogicType {
OR,
AND
}
public enum ResultType {
FORWORD,
JSON_EXCEPTION
}
@Aspect
public class AclRightAspect {
final Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@within(com.taobao.bright.service.system.auth.AclRightAnnotation) && execution(public * *(..))")
public void classPointCut() {}
@Pointcut("@annotation(com.taobao.bright.service.system.auth.AclRightAnnotation) && execution(public * *(..))")
public void actionPointCut() {}
@Around("classPointCut() || actionPointCut()")
public void checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
AclRightAnnotation[] methodAnnotations = methodSig.getMethod().getDeclaredAnnotationsByType(AclRightAnnotation.class);
AclRightAnnotation[] classAnnotations = joinPoint.getTarget().getClass().getDeclaredAnnotationsByType(AclRightAnnotation.class);
List<AclRightAnnotation> annotations = new ArrayList<>();
annotations.addAll(Arrays.asList(classAnnotations));
annotations.addAll(Arrays.asList(methodAnnotations));
boolean hasRight = false;
String right = "";
ResultType resultType = ResultType.JSON_EXCEPTION ;
LogicType logicType = LogicType.OR;
String expr = "";
for(AclRightAnnotation acl : annotations){
resultType = acl.result();
logicType = acl.logic();
right= acl.value().getValue();
expr = acl.expr();
//根据输入参数的值鉴权
if(!ValidateUtils.isEmpty(expr)){
//注入参数变量到ScriptEngine环境
String[] argNames = methodSig.getParameterNames();
Object[] argVals = joinPoint.getArgs();
SimpleBindings simpleBindings = new SimpleBindings();
for(int i=0; i<argNames.length; i++){
simpleBindings.put(argNames[i], argVals[i]);
}
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
//注解中的表达式执行
boolean isProcess = (boolean) engine.eval(expr, simpleBindings);
if(!isProcess){
continue;
}
} catch (Exception e) {
logger.error(e.getMessage(), e.getCause());
continue;
}
}
if(logicType == LogicType.OR ) {
if (AclRightCache.checkCurrentUserRight(right)) {
hasRight = true;
break;
} else {
hasRight = false;
}
}
if(logicType == LogicType.AND){
if(AclRightCache.checkCurrentUserRight(right)){
hasRight = true ;
}else {
hasRight = false;
break;
}
}
}
if(!hasRight){
Object[] methodArgs = joinPoint.getArgs();
for(Object arg: methodArgs){
if(ResultType.JSON_EXCEPTION == resultType) {
if (arg instanceof Context) {
Context context = (Context) arg;
Result<String> result = new Result<>();
result.addActionError("No permission!");
context.put(Result.KEY_SIGN, result);
}
}else {
if (arg instanceof Navigator) {
Navigator nav = (Navigator) arg ;
nav.forwardTo("right/noAclRight").withParameter("aclRightType", right);
}
}
}
}else {
joinPoint.proceed();
}
}
}
使用样例:
//下面代码的实现的鉴权逻辑为:( hasRight(AAA) AND ( domain == 'www.aliyun.com' ? hasRight(BBB) : true ) 。 注意:expr 是js语法的 代码。
@AclRightAnnotation(value = AclRightType.AAA, logic=LogicType.AND )
@AclRightAnnotation(value = AclRightType.BBB, logic=LogicType.AND ,expr="domain == 'www.aliyun.com'")
public class Action {
public void execute(String domain, HttpServletRequest req, ParameterParser params,Navigator nav) throws Exception {
}
}
//权限注解可以加到类或方法上。 加在类上,会对类中所有public方法都生效。 多个权限位之间可以指定逻辑关系 AND/OR,默认为OR。
//下面代码的实现的鉴权逻辑为:( hasRight(AAA) OR ( domain == 'www.aliyun.com' ? hasRight(BBB) : true ) 。 逻辑操作符默认是 OR。
@AclRightAnnotation(value = AclRightType.AAA, )
@AclRightAnnotation(value = AclRightType.BBB ,expr="domain == 'www.aliyun.com'")
public class Action {
public void execute(String domain, HttpServletRequest req, ParameterParser params,Navigator nav) throws Exception {
}
}