用户如何(安全地)在Java中编写自己的过滤器

时间:2012-04-15 13:01:58

标签: java dynamic groovy rhino

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

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

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

我的应用程序非常依赖于这个功能,它会在我的服务器上集中调用 因此,我认为速度是关键。

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

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

更多信息: 我在Google App Engine上运行我的应用程序,用于我的应用程序的主要Web服务 过滤器将通过呼叫应用20次 过滤器(大部分时间)都很简单

是否有任何想法让我的服务器安全过滤? 还有其他任何方法可以让它起作用吗?

3 个答案:

答案 0 :(得分:1)

一些想法:

  • 无论您使用的是JavaScript还是Groovy,它都将在您提供给脚本的上下文中运行,因此脚本不应该能够访问您不想要它的任何内容(当然,你应该进行广泛的测试,以确定是否采用这条路线。)

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

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

答案 1 :(得分:1)

我的想法:

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

只关注上面的两个项目,它看起来非常复杂和脆弱。如果您无法将现有沙盒功能作为现有项目找到,则应远离它。

设计一种特定于领域的语言,允许您认为合法的表达方式更加安全,并且在查看上述内容时,您必须非常努力地思考您想要允许的内容。从那里到设计语言不是一大步。

小心不要使用groovy闭包(内部DSL)来实现DSL,因为这只是时髦而你也是可以破解的。您需要定义一个extrnal语言并解析它。我建议使用解析器组合器jparsec来定义语法。在这种情况下,不需要编译器编译器。

http://jparsec.codehaus.org/

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

    //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())
    }

答案 2 :(得分:0)

我永远不会让用户输入任意代码。这是脆弱,不安全和糟糕的用户体验。我不知道你的用户是什么,我猜你会花很多时间来回答问题。如果你的大部分过滤器很简单,为什么不为它们创建一个小过滤器构建器呢?

至于groovy与JavaScript我认为groovy更容易理解,更适合脚本,但这只是我的意见。