I have a Java web application that's hosted in Amazon Web Services and it's running on the retired Tomcat 8 with Amazon Linux.
I've attempted to create a new environment to test running the app on the latest Tomcat 8.5 with Corretto 11 running on Amazon Linux 2.
During this process I hit a big problem, within my app it's using a base URL, e.g. https://myserver.com and it's doing some string concatenation to add the path e.g. /rest/myendpoint which of course should be resolved as
https://myserver.com/rest/myendpoint
But unfortunately unknowingly it's being passed through C#'s new URL()
which is appending a trailing slash before the concatenation.
So
https://myserver.com/rest/myendpoint
gets sent to the server as
https://myserver.com/rest//myendpoint
(Note the rest//
should be rest/
)
After deploying the Java war file to the new environment some requests from the app fail with a 404. I've analysed these network logs and of course it's all the URLs that have a double-escape in the path.
As a test in Postman writing the request to have a single slash worked fine, double-quotes failed with a HTTP 404, just like the app.
The problem now is that the app is already used in the wild, we can't change the app because this will involve a long delay (getting it built, app store approval process, then waiting for everyone to upgrade the app), most likely months.
I can't find anything in the AWS portal to configure this.
I did try swapping to Nginx but I experienced the same error.
I did see some people mentioning using .eb-extensions, but the whole apache re-write / mod rules are very foreign to me. In effect I want to re-write any double-slashes after http:// or https:// to single-slashes. Can you please advise on how this could be done?
I'm using the Elastic Beanstalk with Tomcat (currently backed with Apache) with the latest Tomcat Platform version - Tomcat 8.5 with Corretto 11 running on 64bit Amazon Linux 2.
Thank you.
Because I didn't really want to faff around with the .ebextensions, I extended an existing servlet filter (the earliest in the chain) to check the path. I was then able to forward using the dispatcher and it works perfectly.
Whilst I'm sure that I can do something cleaner with reg-ex this done the trick for me and I could test it locally with Postman first.
package com.devology.servlet.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class AuthenticationFilter implements Filter {
public AuthenticationFilter() {
}
private boolean doBeforeProcessing(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
HttpServletRequest hsr = (HttpServletRequest) request;
final String path = hsr.getServletPath();
String fullUrl = hsr.getRequestURL().toString();
String remoteAddress = hsr.getRemoteAddr();
// Example URL from bad app...
//
// http://localhost:8084/MyApp//rest/login
//
// path = /rest
// fullUrl = http://localhost:8084/MyApp//rest/login
// indexOfPath = ^ = 34
//
// If the character before the start of the path is also a slash then
// we need to internally forward
int indexOfPath = fullUrl.indexOf(path + "/");
boolean needsUrlRewrite = false;
if (indexOfPath != -1) {
String previousCharacter = fullUrl.substring(indexOfPath - 1, indexOfPath);
needsUrlRewrite = "/".equals(previousCharacter);
}
if (needsUrlRewrite) {
// Just take the path from the point of indexOfPath, .e.g /rest/login
String rewrittenPath = fullUrl.substring(indexOfPath);
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(rewrittenPath);
dispatcher.forward(request, response);
return false;
}
// Any other checks like authorisation, or return true to let all through
return true;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
if (doBeforeProcessing(request, response)) {
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig fc) throws ServletException {
// Your Init
}
@Override
public void destroy() {
// Your cleanup
}
}
This assumes that you have it configured in web.xml like this...
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>com.devology.servlet.filters.AuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspf</url-pattern>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>10</session-timeout>
</session-config>
<error-page>
<error-code>500</error-code>
<location>/error-500.html</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error-404.html</location>
</error-page>
</web-app>
I didn't fix the issue with the .ebextensions and Apache configuration because I didn't really know what I was doing and the above servelet filter done exactly what I needed.