Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access request headers on JAXRS classes generated by Swagger Codegen

I have a project with an Swagger API and its server code was generated by swagger-codegen-2.4.24 for language jaxrs.

The code generated has an abstract class suffixed "*ApiService" that defines a series of methods, each corresponding to each operation defined on the Swagger specification of the API.

Each method has a javax.ws.rs.core.SecurityContext interface local variable.

Now, on my custom class which extends "*ApiService", that obviously has javax.ws.rs.core.SecurityContext class local variable, I need to fetch the value of request header "X-Forwarded-For".

If I debug my custom class I see that SecurityContext interface is an instance of org.glassfish.jersey.server.internal.process.SecurityContextInjectee, which has the header I need.

How do I get that information, since I'm not able to work with SecurityContextInjectee since it's private?

I realize that if classes generated by swagger-codegen added javax.servlet.http.HttpServletRequest class, besides SecurityContext, it would be possible to have access to the request parameters, but I didn't see any jaxrs parameter that allows that.

Looking forward for your comments.

like image 287
RedEagle Avatar asked Oct 26 '22 09:10

RedEagle


1 Answers

In every specification version you can define a header like one of the possible parameter locations.

So, one possible solution, will be to define the header in the methods you required in the request parameters sections:

parameters:
    -
        name: X-Forwarded-For
        description: X-Formarwed-For header.
        schema:
            type: string
        in: header

Or, in JSON notation:

    "parameters": [
        {
            "name": "X-Forwarded-For",
            "description": "X-Formarwed-For header.",
            "schema": {
                "type": "string"
            },
            "in": "header"
        }
    ]

I am aware that perhaps it is a less maintainable solution because you will need to include the header in every request, but maybe you could mitigate that fact with inheritance in your services implementation.

There is an open Github issue asking for the behavior you described, handling the header processing in a general way.

One suitable option, suggested as well in this related SO answer, could be modifying the Mustache templates used in the API code generation and include within them the required headers processing. Please, be aware that this will do your code less maintainable and you will have the risk of perform some change that breaks the compatibility with the official Swagger Codegen repository. I am not sure in Swagger Codegen, but in the OpenAPI generator there is an option to override the used templates without modifying the actual provided in the official distribution. Please, see this related SO question.

Although it seems that is no longer the case, at least in older versions of Jersey in which the class was public, you could try accessing the requestContext internal variable in org.glassfish.jersey.server.internal.process.SecurityContextInjectee by reflection as well, although I think that workaround makes your application very implementation dependent. In any case, perhaps you could define an utility method like this that you could reuse in your services implementation:

public static String getXForwardedForHeaderValue(final SecurityContext securityContext) {
  SecurityContextInjectee securityContextImpl = (SecurityContextInjectee) securityContext;
  Field requestContextField = SecurityContextInjectee.class.getDeclaredField("requestContext");
  requestContextField.setAccessible(true);
  ContainerRequestContext requestContext = requestContextField.get(securityContextImpl);
  String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
  return xForwardedForHeaderValue;
}

Finally, another possibility could be using a filter that process your header. If required you could pass the header value using for instance a thread local variable to the underlying services. The idea would be something like the following.

First, define a convenient object that wraps your ThreadLocal value:

public class XForwardedForHeaderHolder{

    private static final ThreadLocal<String> value = new ThreadLocal<String>();

    public static void setXForwardedForHeader(String xForwardedFor) {
        value.set(xForwardedFor);
    }

    public static String getXForwardedForHeader() {
        return value.get();
    }

    public static void clean() {
        value.remove();
    }
}

Next, create a ContainerRequestFilter. This filter will read header from the information received in the HTTP request being processed:

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
 
@Provider
public class XForwardedForHeaderRequestFilter implements ContainerRequestFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext)
        throws IOException {
 
        String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
        XForwardedForHeaderHolder.setXForwardedForHeader(
            xForwardedForHeaderValue
        );
    }
}

Finally, consume the value in your services implementation:

String xForwardedForHeaderValue = XForwardedForHeaderHolder.getXForwardedForHeader();
// Clean up
XForwardedForHeaderHolder.clean();

A word of caution: on one hand, the filter registration should work properly but it could depend on the JAXRS version you are using and Swagger itself; on the other, the solution assume that the filter will provide, in the thread local variable, the right header for every request to the underlying services, in other words, that there are not any threading related issue. I think it should be the case, but it is something that need to be tested.

like image 177
jccampanero Avatar answered Nov 15 '22 04:11

jccampanero