Spring WebRequest getParameter() 显示为空

Spring WebRequest getParameter() is showing null

我在尝试使用 Spring MVC 控制器 在托管的 Tomcat 服务器 中处理 Mailchimp Webhook 请求时遇到问题(在我自己的服务器中开发环境,一切正常。

我只是在 webhook URL 中添加了一个 "secret" 参数,像这样:

http://doamin/webhook.html?secret=password

Mailchimp 向 webapp 发出 POST 请求,但他们说出于安全原因,您应该向 URL 添加一个 "secret" 密钥(因此是 GET 参数)。

然后我只是在进入业务逻辑之前检查该参数,通过...

@RequestMapping(method=RequestMethod.POST)
public ModelAndView postProcess(WebRequest request){
    if (request.getParameter("secret").equals("password"){
        //business logic
    }
}

这在我自己的本地是可以的tomcat。从Mailchimp请求中正确抓取参数,所有业务逻辑为运行.

但后来我将我的 WAR 上传到生产环境,我可以看到那里的业务逻辑不是 运行。

经过大量有趣的调试...我发现 getParameter("secret") returns me null.

你认为这可能与 tomcat 会议有关吗?

我本地tomcat版本是7.0.67.

我托管的 tomcat 版本是 7.0.62

我的 web.xml 看起来像:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>Servlet 3.0 Web Application</display-name>

  <display-name>Spring Web MVC Application</display-name>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <!--  Load up all spring xml files as part of the loading of the webapp -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/security-config.xml;/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

     <!--  This filter is used by Spring Security to intercept all URL patterns -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <error-page>
        <error-code>403</error-code>
        <location>/Forbidden.html</location>
    </error-page>

    <error-page>
      <exception-type>org.springframework.web.util.NestedServletException</exception-type>
      <location>/JDBCException.html</location>
    </error-page>   

    <!-- Session expiration max time (in minutes) -->
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

</web-app>

我的托管 server.xml 看起来像:

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE server-xml [
  <!ENTITY jelastic-ssl SYSTEM "jelastic-ssl.xml">
  <!ENTITY jelastic-ha SYSTEM "jelastic-ha.xml">
]>

<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<Server port="8005" shutdown="SHUTDOWN">
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
  <Listener className="org.apache.catalina.core.JasperListener" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="443" />

    <!--############## SSL Connector for _domain_name ### PROTO_Dl231aIDsW4 ##########-->
    &jelastic-ssl;
    <!--############## SSL Connector for _domain_name ### PROTO_Dl541aINsMx ##########-->
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL HTTP/1.1 Connector on port 8443
         This connector uses the JSSE configuration, when using APR, the
         connector should be using the OpenSSL style configuration
         described in the APR documentation -->
    <!--
    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="${jvmRid}">
    -->
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="${jvmRid}">

    <!--Jelastic HA clusteting is enabled here so please do not remove this line untill you reasly know what you are doing -->
    &jelastic-ha;
    <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

已编辑: 更多测试:

我已经评论了我要求密钥的代码。那么,业务逻辑当然是运行。但是在该业务逻辑中,我还要求 POST 参数,真是令人惊讶!!!!每次我尝试访问我的 WebRequest 参数时,我也会收到 NullPointerException。

如果我重新启动 tomcat,第一次代码是 运行,它可以工作,但是如果我重复调用,就会出现 NullPointerException。

我有问题的代码架构是这样的:

1) 一个由 MVC 控制器调用的线程进程,因为我需要在不到 15 秒内回答 Mailchimp,而在后台我启动一个线程到 运行 业务逻辑:

package es.edm.util;

import java.util.Date;

import org.springframework.web.context.request.WebRequest;

import es.edm.services.MailingListService;

public class MailingListRequestProcessor implements Runnable {

    private Thread t;
    private WebRequest request;
    private MailingListService mailing;

    @Override
    public void run() {
        System.out.println(new Date() + ": Request status afet calling the thread to start, but before calling business logic" + request);
        mailing.processRequest(request);
    }

    public void start (WebRequest request, MailingListService mailing) {
        if (t == null){
            System.out.println(new Date() + ": Request status before calling the thread to start" + request);
            this.request = request;
            this.mailing = mailing;
            t = new Thread (this, "MailchimpRequest");
            t.start ();
        }
    }
}

2) 控制器,创建线程并响应 Mailchimp 请求:

package es.edm.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

import es.edm.services.MailingListService;
import es.edm.util.MailingListRequestProcessor;

@Controller
@RequestMapping(path="/Webhook")
public class MailingServiceIntegrationController_MailchimpImpl {

    @Autowired
    MailingListService mailing;

    @RequestMapping(method=RequestMethod.GET)
    public ModelAndView getProcess(WebRequest request){
        return new ModelAndView("/web/MailchimpAnswer.jsp");
    }

    @RequestMapping(method=RequestMethod.POST)
    public ModelAndView postProcess(WebRequest request){
        MailingListRequestProcessor processor = new MailingListRequestProcessor();
        processor.start(request, mailing);
        return new ModelAndView("/web/MailchimpAnswer.jsp");
    }
}

3) 以及用于实现业务逻辑的服务 class:

@Override
public void processRequest(WebRequest request) {
    //To recover, once solved the problem with Jelastic and getParameters();
    //if (request.getParameter("secret")!= null){
        //if (request.getParameter("secret").equals(conf.getMailingListSecretPassword())){
            switch (request.getParameter("type")){
            case "subscribe": processSubscribe(request); break;
            case "unsubscribe": processUnsubscribe(request); break;
            case "profile": processProfile(request); break;
            case "upemail": processEmailChange(request); break;
            case "cleaned": processCleanedEmail(request); break;
            case "campaign": processCampaign(request); break;
            }
        //}
    //}
}

catalina 的输出是这样的:

Exception in thread "MailchimpRequest" java.lang.NullPointerException
    at es.edm.services.Impl.MailingListService_Mailchimp_Impl.processRequest(MailingListService_Mailchimp_Impl.java:37)
    at es.edm.util.MailingListRequestProcessor.run(MailingListRequestProcessor.java:15)
    at java.lang.Thread.run(Thread.java:745)

MailingListService_Mailchimp_Impl.java:37 是这样的:

switch (request.getParameter("type")){

request.getParameter("type") 为空...而在我的本地 tomcat 已正确填写。

谢谢!

在您使用 Jelastic 时,请尝试获得 Jelastic 支持。我想在这种情况下他们可以帮助你。

经过长时间的研究,我最终找到了问题所在:

<!-- This should be removed to improve security!!!! -->
<csrf disabled="true"/>

您不能在使用 Mailchimp 时使用 CSRF 保护,因为它需要将 "secret" 参数作为 GET 参数传递,但 CSRF 可以阻止它。