0
votes

I'm writing a REST connector to HP Quality Center using Apache CFX. I'd like to use the CFX infrastructure to do preemptive authentication when a request is made to the server

HP Quality Center uses an Authentication mechanism based on Basic. To authenticate, a get request is sent to http:///qcbin/authentication-point/authenticate with a standard Basic authentication header. The server then returns a cookie ("LWSSO") which must be included in all subsequent requests. Requesting a resource from the server before authenticating will result in a 401 with WWW-Authenticate header that contains the authentication point URI (e.g. LWSSO realm="http://:80/qcbin/authentication-point).

Ideally, I'd like to create a CFX HttpAuthProvider or Interceptor that handles authentication by intercepting a 401 response, parsing out the WWW-Authenticate header and performing the request on the authentication point URI before caching the cookie for all subsequent requests.

This would allow me to create a clean Proxy based API using a factory pattern. For example:

public QualityCenter create(String url, String username, String password) {
    JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
    bean.setAddress(url);
    bean.setUsername(username);
    bean.setPassword(password);
    bean.setServiceClass(QualityCenter.class);      

       // TODO: Setup authentication modules here that use AuthPolicy for credentials.

    return bean.create(QualityCenter.class);
}

I just cant seem to figure out if this is possible and where best to implement the functionality.

1

1 Answers

1
votes

I ended up solving this one without using Apache CXF -- instead opting to go Jersey.

I did this by creating two JAXRS filters. This first filter intercepts 401 responses from the server, follows the authentication point address returned in the "WWW-Authenticate" header and performs the BASIC authentication. The original request is then replayed to the server.

The second part of this equation is another filter that handles maintaining the session cookies. Thus, when the initial request is replayed, the authentication cookie is present.

The two filters look like this:

class AuthenticationFilter implements ClientResponseFilter {

private final String username;
private final String password;

public AuthenticationFilter(String username, String password) {
    this.username = username;
    this.password = password;
}

@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
    if (responseContext.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) {
        String header = responseContext.getHeaders().getFirst(WWW_AUTHENTICATE);

        // Check if we have been given the authentication redirect go-ahead.
        if (!header.startsWith("LWSSO")) {
            return;
        }

        String authUri = header.substring(13, header.length() - 1);
        Client client = ClientBuilder.newClient(requestContext.getConfiguration());
        String credentials = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
        Response response = client.target(authUri).path("authenticate").request(MediaType.TEXT_PLAIN_TYPE).header(AUTHORIZATION, credentials).get();
        if (response.getStatus() == Response.Status.OK.getStatusCode()) {
            URI uri = requestContext.getUri();
            MediaType mediaType = requestContext.getMediaType();
            String method = requestContext.getMethod();
            NewCookie cookie = response.getCookies().get("LWSSO_COOKIE_KEY");
            MultivaluedMap<String, Object> headers = requestContext.getHeaders();
            headers.remove(WWW_AUTHENTICATE);
            Invocation.Builder builder = requestContext.getClient().target(uri).request(mediaType).headers(headers).cookie(cookie);

            Invocation invocation;
            if (requestContext.getEntity() != null) {
                invocation = builder.build(method, Entity.entity(
                        requestContext.getEntity(), mediaType));
            } else {
                invocation = builder.build(method);
            }

            Response replayed = invocation.invoke();
            responseContext.setStatus(replayed.getStatus());
            responseContext.setStatusInfo(replayed.getStatusInfo());
            if (replayed.hasEntity()) {
                responseContext.setEntityStream(replayed
                        .readEntity(InputStream.class));
            }
            responseContext.getHeaders().clear();
            responseContext.getHeaders()
                    .putAll(replayed.getStringHeaders());
        }
    }
}}


class SessionFilter implements ClientRequestFilter, ClientResponseFilter {

    private final Map<String, NewCookie> cookies = new HashMap<String, NewCookie>();

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        cookies.putAll(responseContext.getCookies());
    }

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        for(NewCookie cookie: cookies.values()) {
            requestContext.getHeaders().add("Cookie", cookie);          
        }
    }

}