as the question allready says, I am trying to do digest authentication in android.
Until now i have used the DefaultHttpClient
and it's authentication method (using UsernamePasswordCredentials
and so on), but it is deprecated since Android 5 and will be removed in Android 6.
So i am about to switch from DefaultHttpClient
to HttpUrlConnection
.
Now i am trying to achieve digest authentication, which should work pretty simple as explained here:
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
But the getPasswordAuthentication
gets never called for some reason.
During my search for this problem i found different posts, saying digest authentication is not supported by the HttpUrlConnection
in android, but those posts are from 2010-2012, so i am not sure if this is still true. Also we are using HttpUrlConnection
with digest authentication in our desktop java application, where it does work.
I also found some posts, talking about OkHttp
. OkHttp
seems to be used by Android under the hood (to be more specific the HttpUrlConnectionImpl
). But this HttpUrlConnectionImpl
is a bit strange, it is not even shown in the Eclipse type hierarchy and i am not able to debug it. Also it should be a com.squareup.okhttp.internal.huc.HttpUrlConnectionImpl
, while in android it is a com.android.okhttp.internal.http.HttpUrlConnectionImpl
.
So i am just not able to do digest authentication with this HttpUrlConnection
in android.
Can anyone tell me how to do that without external libraries?
EDIT:
The server asks for digest authentication:
WWW-Authenticate: Digest realm="Realm Name",domain="/domain",nonce="nonce",algorithm=MD5,qop="auth"
So Basic-Authentication shouldn' work, as the server is asking for digest.
The answer is, that HttpUrlConnection
does not support digest.
You therefore have to implement RFC2617 by yourself.
You can use the following code as a baseline implementation: HTTP Digest Auth for Android.
The steps involve (see RFC2617 for reference):
WWW-Authenticate
headers and parse them:
auth
qop option), otherwise ignore the challenge and go to the next header.Authenticator.requestPasswordAuthentication
.Authorization
header to your HttpUrlConnection
.By using Authenticator
, you can make sure, that as soon as HttpUrlConnection
supports digest natively, your code is not being used anymore (because you wont receive the 401 in the first place).
This is just a quick summary on how to implement it, for you to get an idea.
If you want to go further you would probably like to implement SHA256 as well: RFC7616
It is correct that HttpUrlConnection
does not support Digest authentication. If your client must authenticate using Digest, you have a few options:
HttpURLConnection
.okhttp-digest
as an authenticator and you will have transparent Http digest support. If you already use OkHttp or are OK with switching to it this can be an attractive option.HttpClient
which supports Digest. The question explicitly states that HttpClient
is not an option so I include it mostly for completion's sake. Google does not recommend using HttpClient
and has deprecated it.Did you try to set the header manually like:
String basic = "Basic " + new String(Base64.encode("username:password".getBytes(),Base64.NO_WRAP ));
connection.setRequestProperty ("Authorization", basic);
Also be aware of some issues in Jellybeans and a bug when you try to perform a post request: HTTP Basic Authentication issue on Android Jelly Bean 4.1 using HttpURLConnection
EDIT: For Digest authentication
Have a look here https://code.google.com/p/android/issues/detail?id=9579
Especially this might work:
try {
HttpClient client = new HttpClient(
new MultiThreadedHttpConnectionManager());
client.getParams().setAuthenticationPreemptive(true);
Credentials credentials = new UsernamePasswordCredentials("username", "password");
client.getState().setCredentials(AuthScope.ANY, credentials);
List<String> authPrefs = new ArrayList<String>(2);
authPrefs.add(AuthPolicy.DIGEST);
authPrefs.add(AuthPolicy.BASIC);
client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,
authPrefs);
GetMethod getMethod = new GetMethod("your_url");
getMethod.setRequestHeader("Accept", "application/xml");
client.executeMethod(getMethod);
int status = getMethod.getStatusCode();
getMethod.setDoAuthentication(true);
System.out.println("status: " + status);
if (status == HttpStatus.SC_OK) {
String responseBody = getMethod.getResponseBodyAsString();
String resp = responseBody.replaceAll("\n", " ");
System.out.println("RESPONSE \n" + resp);
}
} catch (Exception e) {
e.printStackTrace();
}
I finally replaced the deprecated DefaultHttpClient
with my own implementation of the HttpUrlConnection
and I implemented digest atuhentication
myself, using this as a template.
The finaly code looks something like this:
// requestMethod: "GET", "POST", "PUT" etc.
// Headers: A map with the HTTP-Headers for the request
// Data: Body-Data for Post/Put
int statusCode = this.requestImpl(requestMethod, headers, data);
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED && hasUserNameAndPassword) {
String auth = getResponseHeaderField("WWW-Authenticate");
// Server needs Digest authetication
if(auth.startsWith("Digest")){
// Parse the auth Header
HashMap<String, String> authFields = parseWWWAuthenticateHeader(auth);
// Generate Auth-Value for request
String requestAuth = generateDigestAuth(authFields);
headers.put("Authorization", authStr);
statusCode = this.requestImpl(requestMethod, headers, data);
}
}
So basicly I make a request and if it returns 401, I look, if the server wants digest authentication
and if I have username and password. If thats the case, I parse the auth header of the response, which contains all the necessary informations about the authentication.
To parse the auth header I use some kind of StateMachine
which is described here.
After parsing the response auth header, I generate the request auth header using the informations from the response:
String digestAuthStr = null;
String uri = getURL().getPath();
String nonce = authFields.get("nonce");
String realm = authFields.get("realm");
String qop = authFields.get("qop");
String algorithm = authFields.get("algorithm");
String cnonce = generateCNonce();
String nc = "1";
String ha1 = toMD5DigestString(concatWithSeparator(":", username, realm, password));
String ha2 = toMD5DigestString(concatWithSeparator(":", requestMethod, uri));
String response = null;
if (!TextUtils.isEmpty(ha1) && !TextUtils.isEmpty(ha2))
response = toMD5DigestString(concatWithSeparator(":", ha1, nonce, nc, cnonce, qop, ha2));
if (response != null) {
StringBuilder sb = new StringBuilder(128);
sb.append("Digest ");
sb.append("username").append("=\"").append(username).append("\", ");
sb.append("realm").append("=\"").append(realm).append("\", ");
sb.append("nonce").append("=\"").append(nonce).append("\", ");
sb.append("uri").append("=\"").append(uri).append("\", ");
sb.append("qop").append("=\"").append(qop).append("\", ");
sb.append("nc").append("=\"").append(nc).append("\", ");
sb.append("cnonce").append("=\"").append(cnonce).append("\"");
sb.append("response").append("=\"").append(response).append("\"");
sb.append("algorithm").append("=\"").append(algorithm).append("\"");
digestAuthStr = sb.toString();
}
To generate the Client-Nonce I am using the following code:
private static String generateCNonce() {
String s = "";
for (int i = 0; i < 8; i++)
s += Integer.toHexString(new Random().nextInt(16));
return s;
}
I hope this helps someone. If the code contains any errors, please let me know so I can fix it. But right now it seems to work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With