2

我希望我的用户在用 Java 请求列表时能够编写自己的过滤器。

选项 1)我正在考虑使用 Rhino 编写 JavaScript。
我将用户的过滤器作为 javascript 字符串获取。然后调用isAccepted(myItem)这个脚本。
根据我是否接受该元素的回复。

选项 2)我正在考虑 Groovy。
我的用户可以在文本字段中编写 Groovy 脚本。当我的用户使用此过滤器进行搜索时,Groovy 脚本会在 Java 中编译(如果第一次调用)并调用 Java 方法isAccepted() ,具体取决于我是否接受该元素的回复。

我的应用程序在很大程度上依赖于这种功能,它将在我的服务器上被密集调用。
所以我相信速度是关键。

选项 1 思考: 如果我错了,请纠正我,但我认为 Groovy 的主要优势是速度,但我的用户可以在我的服务器上编译和运行不需要的代码......(任何解决方法?)

选项 2 思考: 我认为在大多数人看来 JavaScript 更像是一个玩具。即使这根本不是我的主意也可能是因为我的客户不会那么信任它。你认为是吗?
根据我在网上的阅读,我期望的另一个坏点是速度。
再次,我的用户可以访问 Java并在我的服务器上运行不需要的代码......(任何解决方法?)

更多信息:我正在 Google App Engine 上为我的应用程序的主要网络服务运行我的应用程序。
过滤器将通过调用应用 20 次。
过滤器(大多数时候)很简单。

有什么想法可以让这个过滤器对我的服务器安全吗?
还有其他方法可以使它工作吗?

4

3 回答 3

1

一些想法:

  • 无论您使用 JavaScript 还是 Groovy,它都将在您提供给脚本的上下文中运行,因此脚本应该无法访问您不希望它访问的任何内容(当然,您应该对其进行广泛测试以一定要走这条路)。

  • 如果可能的话,将过滤器表达式指定为数据而不是可执行代码可能会更安全。当然,这取决于过滤器表达式的复杂程度。也许您可以将表示分解为字段、比较器和值之类的东西,或者类似的东西,可以将其视为数据并以常规方式进行评估?

  • 如果您担心用户可以通过脚本语言注入什么,那么使用 JavaScript 可能更安全。我不认为性能应该是一个问题,但我再次建议进行广泛的测试以确定。

于 2012-04-15T15:22:24.413 回答
1

我的想法:

  • 在编译脚本时,您必须使用自己的类加载器,以避免从脚本中访问任何其他类。不确定在 GAE 中是否可行。
  • 您必须使用 Java 的 SecurityManager 功能来避免脚本能够访问文件系统、网络等。不确定在 GAE 中是否可行。

只看上面的两个项目,对我来说它看起来非常复杂和脆弱。如果你找不到现有的沙盒功能作为现有项目,你应该远离它。

设计一种允许您认为合法的表达式的领域特定语言要安全得多,并且查看上述项目,无论如何您都必须非常努力地思考您想要允许的内容。从那里到设计语言并不是一个很大的步骤。

注意不要使用 groovy 闭包(内部 DSL)来实现 DSL,因为那只是 groovy,而且你也可以破解。您需要定义一种外部语言并对其进行解析。我推荐使用解析器组合器 jparsec 来定义语法。在这种情况下不需要编译器编译器。

http://jparsec.codehaus.org/

仅供参考,这是我用 jparsec 编写的一个小解析器(常规代码):

    //import some static methods, this will allow more concise code
    import static org.codehaus.jparsec.Parsers.*
    import static org.codehaus.jparsec.Terminals.*
    import static org.codehaus.jparsec.Scanners.*

    import org.codehaus.jparsec.functors.Map as FMap
    import org.codehaus.jparsec.functors.Map4 as FMap4
    import org.codehaus.jparsec.functors.Map3 as FMap3
    import org.codehaus.jparsec.functors.Map2 as FMap2

    /**
     * Uses jparsec combinator parser library to construct an external DSL parser for the following grammar:
     * <pre>
     *     pipeline := routingStep*
     *     routingStep := IDENTIFIER '(' parameters? ')'
     *     parameters := parameter (',' parameter)*
     *     parameter := (IDENTIFIER | QUOTED_STRING)  ':' QUOTED_STRING
     * </pre>
     */
    class PipelineParser {
        //=======================================================
        //Pass 1: Define which terminals are part of the grammar
        //=======================================================
        //operators
        private static def OPERATORS = operators(',', '(', ')', ':')
        private static def LPAREN = OPERATORS.token('(')
        private static def RPAREN = OPERATORS.token(')')
        private static def COLON = OPERATORS.token(':')
        private static def COMMA = OPERATORS.token(',')

        //identifiers tokenizer
        private static def IDENTIFIER = Identifier.TOKENIZER
        //single quoted strings tokenizer
        private static def SINGLE_QUOTED_STRING = StringLiteral.SINGLE_QUOTE_TOKENIZER

        //=======================================================
        //Pass 2: Define the syntax of the grammar
        //=======================================================
        //PRODUCTION RULE: parameter := (IDENTIFIER | QUOTED_STRING) ':' QUOTED_STRING
        @SuppressWarnings("GroovyAssignabilityCheck")
        private static def parameter = sequence(or(Identifier.PARSER,StringLiteral.PARSER), COLON, StringLiteral.PARSER, new FMap3() {
            def map(paramName, colon, paramValue) {
                new Parameter(name: paramName, value: paramValue)
            }
        })

        //PRODUCTION RULE: parameters := parameter (',' parameter)*
        @SuppressWarnings("GroovyAssignabilityCheck")
        private static def parameters = sequence(parameter, sequence(COMMA, parameter).many(), new FMap2() {
            def map(parameter1, otherParameters) {
                if (otherParameters != null) {
                    [parameter1, otherParameters].flatten()
                } else {
                    [parameter1]
                }
            }
        })

        //PRODUCTION RULE: routingStep := IDENTIFIER '(' parameters? ')'
        @SuppressWarnings("GroovyAssignabilityCheck")
        private static def routingStep = sequence(Identifier.PARSER, LPAREN, parameters.optional(), RPAREN, new FMap4() {
            def map(routingStepName, lParen, parameters, rParen) {
                new RoutingStep(
                    name: routingStepName,
                    parameters: parameters ?: []
                )
            }
        })

        //PRODUCTION RULE: pipeline := routingStep*
        @SuppressWarnings("GroovyAssignabilityCheck")
        private static def pipeline = routingStep.many().map(new FMap() {
            def map(from) {
                new Pipeline(
                    routingSteps: from
                )
            }
        })

        //Combine the above tokenizers to create the tokenizer that will parse the stream and spit out the tokens of the grammar
        private static def tokenizer = or(OPERATORS.tokenizer(), SINGLE_QUOTED_STRING, IDENTIFIER)

        //This parser will be used to define which input sequences need to be ignored
        private static def ignored = or(JAVA_LINE_COMMENT, JAVA_BLOCK_COMMENT, WHITESPACES)

        /**
         * Parser that is used to parse extender pipelines.
         * <pre>
         *     def parser=PipelineParser.parser
         *     Pipeline pipeline=parser.parse(pipelineStr)
         * </pre>
         * Returns an instance of {@link Pipeline} containing the AST representation of the parsed string.
         */
        //Create a syntactic pipeline parser that will use the given tokenizer to parse the input into tokens, and will ignore sequences that are matched by the given parser.
        static def parser = pipeline.from(tokenizer, ignored.skipMany())
    }
于 2012-04-15T22:20:59.853 回答
0

我永远不会让用户输入任意代码。它脆弱、不安全且用户体验不佳。对你的用户一无所知,我猜你会花很多时间回答问题。如果你的大多数过滤器都很简单,为什么不为他们创建一个小的过滤器构建器呢?

就 groovy 与 JavaScript 而言,我认为 groovy 更容易理解并且更适合编写脚本,但这只是我的看法。

于 2012-04-19T05:08:32.090 回答