No form params in rest resource for form urlencoded when using Content, but application/json works?

756 Views Asked by At

I have a REST endpoint @POST where the form params are null when the Content-Type is application/x-www-form-urlencoded. There is a ContainerRequestFilter earlier in the chain (code at the bottom) that takes the request, changes the stream to a BufferedInputStream, and then logs the request. If I remove this logging code, the endpoint has the correct form params. Otherwise, they're null and I can't figure out why.

Now if I use application/json, my endpoint has the correct params regardless if the logger is enabled or disabled.

I need application/x-www-form-urlencoded because the REST endpoint needs to redirect and browsers prevent redirection if the request isn't standard (preflight)

REST Endpoint that isn't working (OAuthRequest has null members)

@Stateless
@Path("v1/oauth2")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public class OAuthTokenResource {

@POST
public Response getToken(@Form OAuthRequest oauthRequest) {
    ...
}

OAuthRequest

public class OAuthRequest {

    @FormParam(OAuthParam.CLIENT_ID)
    @JsonProperty(OAuthParam.CLIENT_ID)
    private String clientId;

    @URL
    @FormParam(OAuthParam.REDIRECT_URI)
    @JsonProperty(OAuthParam.REDIRECT_URI)
    private String redirectUri;

    @FormParam(OAuthParam.USERNAME)
    private String username;

    @FormParam(OAuthParam.PASSWORD)
    private String password;
    ...
}

Logging Filter

@Override
public void filter(final ContainerRequestContext context) throws IOException {
    ...
    if (logEntity && context.hasEntity()) {
        context.setEntityStream(logInboundEntity(builder, context.getEntityStream(), context.getMediaType()));
    }

    logger.debug(builder.toString());
}

private InputStream logInboundEntity(final StringBuilder builder, InputStream stream, MediaType mediaType) throws IOException {
    if (!stream.markSupported()) {
        stream = new BufferedInputStream(stream);
    }

    stream.mark(maxEntitySize + 1);
    final byte[] entity = new byte[maxEntitySize + 1];
    final int entitySize = stream.read(entity);

    if ( entitySize > 0 ) {
        String body = new String(entity, 0, Math.min(entitySize, maxEntitySize), StandardCharsets.UTF_8);
        builder.append("\nBody: ");
        builder.append(body);
    }

    if (entitySize > maxEntitySize) {
        builder.append(MORE_INDICATOR);
    }

    stream.reset();

    return stream;
}
1

There are 1 best solutions below

0
On

Okay I am still not sure why @Form and @FormParam does not work if the InputStream is read during the filter chain.

But, I discovered a workaround as follows.

@POST
public Response getToken(MultivaluedMap<String, String> formParams) {
    ...
}

This provides the same behavior as during application/json as the params are already set even if the InputStream has been consumed.

However ultimately we went with disabling logging of the request body in our filter for security reasons.