Android - Configuring Retrofit/Apache HttpClient for Digest Auth

1.4k views Asked by At

I am working on an Android project and trying to get Digest Authentication to work with Retrofit. I'm kind of amazed Retrofit doesn't natively support it (or more accurately, that OkHttp doesn't support it), but no point complaining I suppose.

I cruised through quite a few threads here and it appears the right solution is to integrate the Apache HttpClient (which natively supports Digest Auth) with Retrofit. This requires wrapped the HttpClient with a retrofit.client.Client implementation. The retrofit incoming values have to be parsed and built into a new HttpClient response which is then sent back to Retrofit to be processed normally. Credit to Jason Tu and his example at: https://gist.github.com/nucleartide/24628083decb65a4562c

Issue is, it isn't working. I'm getting a 401 Unauthorized every time and it's not clear to me why. Here's my Client impl:

public class AuthClientRedirector implements Client {
    private final CloseableHttpClient delegate;

    public AuthClientRedirector(String user, String pass, String hostname, String scope) {
        Credentials credentials = new UsernamePasswordCredentials(user, pass);
        AuthScope authScope = new AuthScope(hostname, 443, scope);

        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(authScope, credentials);

        delegate = HttpClientBuilder.create()
        .setDefaultCredentialsProvider(credentialsProvider)
        .build();
   }

    @Override 
    public Response execute(Request request) {
    //
    // We're getting a Retrofit request, but we need to execute an Apache
    // HttpUriRequest instead. Use the info in the Retrofit request to create
    // an Apache HttpUriRequest.
    //
    String method = request.getMethod();

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    if (request.getBody() != null) {
        try {
            request.getBody().writeTo(bos);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    String body = new String(bos.toByteArray());

    HttpUriRequest wrappedRequest;
    switch (method) {
    case "GET":
        wrappedRequest = new HttpGet(request.getUrl());
        break;
    case "POST":
        wrappedRequest = new HttpPost(request.getUrl());
        wrappedRequest.addHeader("Content-Type", "application/xml");
        try {
            ((HttpPost) wrappedRequest).setEntity(new StringEntity(body));
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        break;
    case "PUT":
        wrappedRequest = new HttpPut(request.getUrl());
        wrappedRequest.addHeader("Content-Type", "application/xml");
        try {
            ((HttpPut) wrappedRequest).setEntity(new StringEntity(body));
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        break;
    case "DELETE":
        wrappedRequest = new HttpDelete(request.getUrl());
        break;
    default:
        throw new AssertionError("HTTP operation not supported.");
    }

CloseableHttpResponse apacheResponse = null;
try {
    apacheResponse = delegate.execute(wrappedRequest);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

if(apacheResponse!=null){
    // Perform the HTTP request.
    CloseableHttpResponse response = null;
    try {
        response = delegate.execute(wrappedRequest);

        // Return a Retrofit response.
        List<Header> retrofitHeaders = toRetrofitHeaders(
                response.getAllHeaders());
        TypedByteArray responseBody;
        if (response.getEntity() != null) {
            responseBody = new TypedByteArray("",
                    toByteArray(response.getEntity()));
        } else {
            responseBody = new TypedByteArray("",
                    new byte[0]);
        }
        System.out.println("this is the response");
        System.out.println(new String(responseBody.getBytes()));
        return new retrofit.client.Response(request.getUrl(),
                response.getStatusLine().getStatusCode(),
                response.getStatusLine().getReasonPhrase(), retrofitHeaders,
                responseBody);
    } catch (ClientProtocolException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        if (response != null) {
            try {
                response.close();                       
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }   
}
    //failed to return a new retrofit Client
    return null;
}

private List<Header> toRetrofitHeaders(org.apache.http.Header[] headers) {
    List<Header> retrofitHeaders = new ArrayList<>();
    for (org.apache.http.Header header : headers) {
        retrofitHeaders.add(new Header(header.getName(), header.getValue()));
    }
    return retrofitHeaders;
}

private byte[] toByteArray(HttpEntity entity) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    entity.writeTo(bos);
    return bos.toByteArray();
}

}

My retrofit configuration looks like this:

public final RestAdapter configureService(){

AuthClientRedirector digestAuthMgr = new AuthClientRedirector(username,password,"myhostname","public");

RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint("http://myhostname:8003/endpoint")
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(digestAuthMgr);
return builder.build();
}

I am stumped why I'm consistently getting 401s back from the server. I've walked through the response building process and it looks clean to me, so I'm thinking I'm missing something fundamental. The credentials are good and I have verified them outside the app. Anyone walked this walk before?

1

There are 1 answers

0
Stanley Ko On

You are using port number 443 for authentication.

AuthScope authScope = new AuthScope(hostname, 443, scope);

But, it seems that your real port number is 8003.

RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint("http://myhostname:8003/endpoint")

So, how about use port number 8003 for authentication like below?

AuthScope authScope = new AuthScope(hostname, 8003, scope);