AOP/Annotation/ScriptEngine 实现复杂鉴权 逻辑,支持自定义逻辑操作和条件表达式的的鉴权

继昨天发布了使用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 {
     
    }
}
上一篇:经典算法题每日演练——第四题 最长公共子序列


下一篇:MySQL第三方客户端工具