Accessing Tomcat JAAS-secured (Form-based) JAX-RS endpoint, from within Java EE client

Unlike my previous post here, sometimes we do need to access JAX-RS resource which is secured by the “traditional” approach of using FORM-based authentication (i.e. jsecuritycheck).

I thought it would be good to share how I do it from within Java client.

Please note that this post is based on (Bien 2013).

Firstly, the thing about ‘jsecurity’-check in Tomcat…

After much reading, I found some help from the following entry at a href=”www.stackoverflow.com” target=”_blank”>stackoverflow.com:

that to get some resource on the tomcat server with j_security_check auth it is necessary to implement three steps:

  1. Send GET request to the needed private resource, in response you get a cookie (Header “Set cookie”)
  2. Send request with cookie (from step 1) to the j_security_check. On response you should get code 302 – “Moved Temporarily”
  3. Now you can repeat request to the private resource with same cookie, on responce you get needed resource data.

(Alexander 2011)

Let’s look at some code

I have put comments in the code to explain what is going on in relation to the three points above. But basically what is happening is that we intercept (Kops 2013) the request in order to first make another request authenticating against jsecuritycheck.

– Register the ‘Authenticator’ when making the call

final Client client = ClientBuilder.newClient().register(new Authenticator("http://myapplication.com/myweb", "username", "password"));
final WebTarget target = client.target("http://myapplication.com/myweb/resources/person");
final JsonObject response = target.request(MediaType.APPLICATION_JSON_TYPE).get(JsonObject.class);
// do something with the response

– Our ‘Authenticator’, which is a ‘javax.ws.rs.client.ClientRequestFilter’

package id.co.lucyana.hr.util;

// imports omitted

@Provider
public final class Authenticator implements ClientRequestFilter {
    private static final String COOKIE = "Cookie";

    // security params
    private static final String J_SECURITY_CHECK = "j_security_check";
    private static final String J_USERNAME = "j_username";
    private static final String J_PASSWORD = "j_password";

    private final String username;
    private final String password;
    private final String baseUri;
    
    // requires by @Provider
    public Authenticator() {
        this.username = null;
        this.password = null;
        this.baseUri = null;
    }

    public Authenticator(final String baseUri, final String username, final String password) {
        this.username = username;
        this.password = password;
        this.baseUri = baseUri;
    }

    @Override
    public void filter(final ClientRequestContext requestContext) throws IOException {
        final List<Object> cookies = new ArrayList<>();

        /*
         * This is hitting the URL as requested by the ClientBuilder.
         * (Refer to the line: "1. Send GET request to the needed private resource,
         * in response you get a cookie (Header “Set cookie”)"
         */
        final Client initialClient =  ClientBuilder.newClient();
        final Response responseInitial = initialClient
                .target(requestContext.getUri())
                .request(requestContext.getAcceptableMediaTypes().get(0))
                .method(requestContext.getMethod());
        initialClient.close();

        /*
         * This section is getting the cookie above and use it to make the call against jsecuritycheck
         * (Refer to the line: 2. Send request with cookie (from step 1) to the j_security_check.
         * On response you should get code 302 – “Moved Temporarily”)
         */
        responseInitial.getCookies().values().stream().forEach((cookie) -> {
            cookies.add(cookie.toCookie());
        });
        final Client loginClient = ClientBuilder.newClient();
        final Form form = new Form();
        form.param(J_USERNAME, this.username);
        form.param(J_PASSWORD, this.password);
        final Response loginResponse = loginClient.target(this.baseUri).path(J_SECURITY_CHECK)
                .request(MediaType.APPLICATION_FORM_URLENCODED)
                .header(COOKIE, cookies)
                .post(Entity.form(form));
        loginClient.close();
        loginResponse.getCookies().values().stream().forEach((cookie) -> {
            cookies.add(cookie.toCookie());
        });

        /*
         * This is right before making the actual call. So we add the cookie (which the server will be
         * able to tell that this call has been authenticated).
         * (Refer to the line: 3. Now you can repeat request to the private resource with same cookie,
         * on responce you get needed resource data.)
         */
        requestContext.getHeaders().put(COOKIE, cookies);
    }
}

References

Alexander, 2011, ‘Request to j_security_check return 408 error only with right paramters’, stackoverflow.com, accessed on 28 November 2016

Bien, A, 2013, ‘Client-side HTTP Basic Authentication With JAX-RS 2.0’, Adam Bien’s weblog, accessed on 28 November 2016

Kops, M, 2013, ‘JAX-RS 2.0 REST Client Features by Example’, hasCode.com, accessed on 28 November 2016