6

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.

RedEagle
  • 4,418
  • 9
  • 41
  • 64

1 Answers1

4

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.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Thank you for the reply! We are using 2.0 specification. I am aware I can set the header on each API specification and it works. The problem is that I would need to add that header on every API operation and we have 13 APIs on the project with a total of around 150 API operations. With that approach, we would need to add that local variable on every API operation method of server-generated code. Besides that, we would need to ask our many clients to regenerate their client SDK which is not an option, unfortunatelly. If I could just extract it from SecurityContextInjectee since it's there... – RedEagle Dec 06 '21 at 14:49
  • Thank you very much for the feedback. Yes, in that situation, I agree with you, including the header in every method is not a suitable solution. Please, could you further describe your setup? I assume you are using jaxrs with jersey2? Are you using the maven plugin? – jccampanero Dec 06 '21 at 15:03
  • Maybe you could try accessing the `requestContext` internal variable in `org.glassfish.jersey.server.internal.process.SecurityContextInjectee` by reflection, although I think that workaround makes your application very implementation dependent and could be the cause of problems. What about using a [filter](https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/filters-and-interceptors.html)? I haven't use them in a swagger generated API, but it could be worth value try implementing one that process your header. If required, maybe using threadlocal variable, you could ... – jccampanero Dec 06 '21 at 15:21
  • try providing the value processed to the underlying API services. Please, be aware that I have never used a solution like this in a similar context, although I did in different ones and it worked properly. – jccampanero Dec 06 '21 at 15:23
  • @RedEagle I updated the answer with further alternatives. I hope it helps. – jccampanero Dec 06 '21 at 16:35
  • Thank you so much for such a complete list of options. At least one of them will make me reach my final objective without any changes to the swagger specs. Accepted as the correct answer. Will let you know my final approach – RedEagle Dec 07 '21 at 19:37
  • Hi @RedEagle. You are welcome. I am very happy to hear that you were able to solve the problem and that the answer was helpful. – jccampanero Dec 07 '21 at 22:04
  • SecurityContextInjectee is a private class so I'm unable to access it, any idea? – RedEagle Dec 09 '21 at 12:11
  • Sorry for the late reply @RedEagle. You are totally right, I am sorry: the class used to be `public` in previous versions of Jersey, but it seems that is no longer the case, now it is package scoped and you haven't access to it. Sorry. I tried providing an alternative implementation based on filters in the answer. I hope it helps. – jccampanero Dec 09 '21 at 22:18
  • No problem @jccampanero! I solved this using your filter suggestion and it works like a charm. Thank you! – RedEagle Dec 13 '21 at 18:30
  • Thank you @RedEagle. That is great. I am very happy to hear that at least the filter approach worked properly. – jccampanero Dec 13 '21 at 18:51