Spring's flashAttributes don't work behind

2019-06-10 05:28发布

问题:

I am experiencing a problem while trying to make the RedirectAttributes' flashAttributes work. I've setup a website built with Spring MVC on Tomcat 7.0 and a reverse proxy using Apache mod_proxy and ajp.

The problem I am facing is also described in this question, but the answer provided there, simply does not apply in my case (I am using a single instance of Tomcat).

This is a snippet from the controller I am using for testing purposes:

@RequestMapping(value = "/land", method = RequestMethod.GET)
    public String land(RedirectAttributes redirectAttrs, Model model) {
    return "redirect_landing";
}

@RequestMapping(value = "/redirect", method = RequestMethod.GET)
public String redirect(RedirectAttributes redirectAttrs, HttpSession session) {

    // add a session message
    session.setAttribute("sessionMessage", "a session message");

    // add a flash message
    redirectAttrs.addFlashAttribute("flashMessage", "a flash message");

    // define the base url
    String baseUrl = "http://localhost:8080/MyApp/";
    // String baseUrl = "http://dev.myapp.lan/";

    return "redirect:" + baseUrl + "land";
}

And the template is as simple as this:

Flash message: ${flashMessage}
Session message: ${sessionMessage}

The same code gives different results, depending on whether I am accessing the website directly on Tomcat or via the apache reverse proxy:

Tomcat's response:
Flash message: a flash message
Session message: a session message

Behind apache mod_proxy:
Flash message:
Session message: a session message

Why is there no flash message when accessing the website via the proxy?

I checked out the code for RedirectAttributesModelMap.java and ModelMap.java but there is not enough info there (obviously the logic is implemented elsewhere).

Note: I can always fall back to the session attributes to achieve my goal, but this issue feels interesting enough for those who use Tomcat behind a reverse proxy


Proxy Configuration (snippet):

<VirtualHost *:80>
    ServerName dev.myapp.lan    

    ProxyPass / ajp://localhost:8009/MyApp/

    ProxyPassReverseCookiePath /MyApp /
    ProxyPassReverseCookieDomain localhost MyApp

    ErrorLog /var/log/apache2/phonebook-error.log
    LogLevel warn

    CustomLog /var/log/apache2/phonebook-access.log combined
</VirtualHost> 

TIA.

回答1:

Changing the context path in the reverse proxy is often to recipe for problems. Assuming that this is the issue you have two options.

  1. Deploy your app as the ROOT application on Tomcat by renaming MyApp.war to ROOT.war (or the MyApp directory to ROOT if it is a directory).

  2. Fire up a suitable tool to look at the HTTP headers and content (Wireshark, FireBug, ieHttpHeaders, etc. - pick one that works for you and your environment) and find all the places where the path needs to be changed and hasn't been. Use mod_headers and mod_substitute (or equivalents) to make the necessary changes in the reverse proxy.

Personally, I always opt for 1 as it is simpler, quicker, easier and far less error prone. I have spend days helping customers debug issues when they have insisted that they have to change the context path in the reverse proxy.

Why does this happen?

When the context path changes in the reverse proxy there are many places where that path might need to be changed:

  1. The request URL that is received from the user agent
  2. A redirect location that is returned by the back-end server
  3. URLs for links in web pages
  4. Custom HTTP response headers set by the application (or a library the application is using)
  5. Cookie paths

ProxyPass handles 1

ProxyPassReverse handles Location, Content-Location and URI headers (2)

ProxyPassReverseCookiePath handles 5

mod_proxy does not handle case 4 or case 5 because this is hard to get right. You end up having to write some very careful regular expressions on a case by case basis to get the desired results.

I suspect that the flashAttributes are using a custom HTTP header that includes the path and that is why they don't work. The session will work as that is normally managed by the session cookie and you have configured ProxyPassReverseCookiePath.