0
votes

I have a Tomcat server with Tuckey installed acting as a proxy for forwarding requests to an application with an embedded Jetty server. The application with embedded Jetty exposes a REST url but does not offer authorisation. Thus, I need to distinguish between requests to admin urls and non-admin urls. Inside Tomcat, I have created an application called "myapp" that contains a filter for Tuckey. Here's the current urlrewrite.xml:

<?xml version="1.0" encoding="UTF-8"?>
<urlrewrite>
    <rule match-type="regex">
        <note>Allow all these requests</note>
        <from>^/v1/api/(one|two|three)/(.*)$</from>
        <to type="proxy" encode="false">http://admin-user:admin-password@localhost:8091/myapp/v1/api/$1/${escapePath:UTF-8:$2}</to>
    </rule>
    <rule match-type="regex">
        <note>These requests must be authorised</note>
        <from>^/v1/api/(four|five|six)(.*)$</from>
        <to type="proxy" encode="false">http://localhost:8091/myapp/v1/api/$1${escapePath:UTF-8:$2}</to>
    </rule>
</urlrewrite>

This generally works - unless a user sends unnecessary user credentials to the first rule. In this case, the basic authentication request from the user is forwarded to the Jetty causing a unauthorized response. If the user could simply omit the username and password (which unfortunately is not always possible because this behavior may be enforced by the client's logic) then this would not happen and the request would go through smoothly.

Is there any solution to this? Can I remove the basic authentication header somehow in Tuckey? The set command apparently does not allow to set auth-type or similar.

1
Did you try the obvious <set type="header" name="WWW-Authenticate"></set>? Or maybe an unpaired tag with no (empty) text child? - Christopher Schultz

1 Answers

0
votes

Finally, a colleague helped me to get this done.

The urlrewrite.xml looks like above:

<?xml version="1.0" encoding="UTF-8"?>
<urlrewrite>
    <rule match-type="regex">
        <note>Allow all these requests</note>
        <from>^/v1/api/(one|two|three)/(.*)$</from>
        <to type="proxy" encode="false">http://admin-user:admin-password@localhost:8091/myapp/v1/api/$1/${escapePath:UTF-8:$2}</to>
    </rule>
    <rule match-type="regex">
        <note>These requests must be authorised</note>
        <from>^/v1/api/(four|five|six)(.*)$</from>
        <to type="proxy" encode="false">http://localhost:8091/myapp/v1/api/$1${escapePath:UTF-8:$2}</to>
    </rule>
</urlrewrite>

As you can see there are two different types of URLs for the "public" and for the "private" REST URLs. For the private URL everything works fine, it's just forwarded to my local Jetty server running at 8091. For the public URL username & password are injected into the URL. This works fine if someone does not provide username & password. Yet, what happens in case someone actually does provide them? Unfortunately, in this case still authorization is triggered in Jetty which fails in consequence (unless username & password are admins). This is not (yet) what we want.

The solution is to remove the authorization header from the original request. Inside class org.tuckey.web.filters.urlrewrite.RequestProxy there is the method setupProxyRequest(HttpServletRequest, URL). Note the added else if block inside the while loop that drops the authorization header in certain cases.

private static HttpMethod setupProxyRequest(final HttpServletRequest hsRequest, final URL targetUrl) throws IOException {
    final String methodName = hsRequest.getMethod();
    final HttpMethod method;
    if ("POST".equalsIgnoreCase(methodName)) {
        PostMethod postMethod = new PostMethod();
        InputStreamRequestEntity inputStreamRequestEntity = new InputStreamRequestEntity(hsRequest.getInputStream());
        postMethod.setRequestEntity(inputStreamRequestEntity);
        method = postMethod;
    } else if ("GET".equalsIgnoreCase(methodName)) {
        method = new GetMethod();
    } else {
        log.warn("Unsupported HTTP method requested: " + hsRequest.getMethod());
        return null;
    }

    method.setFollowRedirects(false);
    method.setPath(targetUrl.getPath());
    method.setQueryString(targetUrl.getQuery());

    String userInfo = targetUrl.getUserInfo();

    Enumeration e = hsRequest.getHeaderNames();
    if (e != null) {
        while (e.hasMoreElements()) {
            String headerName = (String) e.nextElement();
            if(headerName != null) headerName = headerName.trim();
            if ("host".equalsIgnoreCase(headerName)) {
                //the host value is set by the http client
                continue;
            } else if ("content-length".equalsIgnoreCase(headerName)) {
                //the content-length is managed by the http client
                continue;
            } else if ("accept-encoding".equalsIgnoreCase(headerName)) {
                //the accepted encoding should only be those accepted by the http client.
                //The response stream should (afaik) be deflated. If our http client does not support
                //gzip then the response can not be unzipped and is delivered wrong.
                continue;
            } else if (headerName.toLowerCase().startsWith("cookie")) {
                //fixme : don't set any cookies in the proxied request, this needs a cleaner solution
                continue;
            }
            else if (headerName.toLowerCase().contains("authorization") && userInfo != null) {
                // Removed authorization header when userInfo is present
                continue;
            }

            Enumeration values = hsRequest.getHeaders(headerName);
            while (values.hasMoreElements()) {
                String headerValue = (String) values.nextElement();
                log.info("setting proxy request parameter:" + headerName + ", value: " + headerValue);
                method.addRequestHeader(headerName, headerValue);
            }
        }
    }
    if (userInfo != null) {
        String headerValue = "Basic " + new String(Base64.encodeBase64(userInfo.getBytes()));
        if ( log.isInfoEnabled())  log.info("setting proxy request parameter: Authorization, value: " + headerValue);
        method.addRequestHeader("Authorization", headerValue);
    }
    if ( log.isInfoEnabled() ) log.info("proxy query string " + method.getQueryString());
    return method;
}

The web.xml looks like this:

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>URL Rewriting proxy</display-name>
    <servlet>
        <servlet-name>default</servlet-name>
        <!-- Standard Tomcat default servlet -->
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <!-- Standard Tuckey default UrlRewriteFilter class -->
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
</web-app>