Digest Authentication In Android Using Httpurlconnection
Solution 1:
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):
- If you get a 401 response, iterate over all
WWW-Authenticate
headers and parse them:- Check if algorithm is MD5 or undefined, (optionally select the
auth
qop option), otherwise ignore the challenge and go to the next header. - Get the credentials using
Authenticator.requestPasswordAuthentication
. - Calculate H(A1) using the username, realm and password.
- Store the canonical root URL, realm, HA1, username, nonce (+ optionally algorithm, opaque and the client selected qop option if present).
- Retry the request.
- Check if algorithm is MD5 or undefined, (optionally select the
- On each request, iterate over all realms you have session information stored for by canonical root URL:
- Calculate H(A2) using the request method and path.
- Calculate H(A3) using HA1, nonce (+ optionally nc, cnonce, qop) and HA2.
- Build and add the
Authorization
header to yourHttpUrlConnection
.
- Implement some sort of session pruning.
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
Solution 2:
It is correct that HttpUrlConnection
does not support Digest authentication. If your client must authenticate using Digest, you have a few options:
- Write your own HTTP Digest implementation. This can be a good option if you know which servers that you need to authenticate with and can ignore the parts of the the digest specification that you do not need. Here is an example where a subset of digest is implemented: https://gist.github.com/slightfoot/5624590.
- Use the external lib bare-bones-digest, which is a Digest lib for Android. You can use it to parse Digest challenges and generate responses to them. It supports the common digest use cases and some of the rarely used ones and can be used on top of
HttpURLConnection
. - Use OkHttp together with okhttp-digest, which is a plugin that adds Http Digest support to OkHttp. Supporting Digest with OkHttp is easy, just add
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. - Use the Apache
HttpClient
which supports Digest. The question explicitly states thatHttpClient
is not an option so I include it mostly for completion's sake. Google does not recommend usingHttpClient
and has deprecated it.
Solution 3:
Did you try to set the header manually like:
String basic = "Basic " + newString(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 {
HttpClientclient=newHttpClient(
newMultiThreadedHttpConnectionManager());
client.getParams().setAuthenticationPreemptive(true);
Credentialscredentials=newUsernamePasswordCredentials("username", "password");
client.getState().setCredentials(AuthScope.ANY, credentials);
List<String> authPrefs = newArrayList<String>(2);
authPrefs.add(AuthPolicy.DIGEST);
authPrefs.add(AuthPolicy.BASIC);
client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,
authPrefs);
GetMethodgetMethod=newGetMethod("your_url");
getMethod.setRequestHeader("Accept", "application/xml");
client.executeMethod(getMethod);
intstatus= getMethod.getStatusCode();
getMethod.setDoAuthentication(true);
System.out.println("status: " + status);
if (status == HttpStatus.SC_OK) {
StringresponseBody= getMethod.getResponseBodyAsString();
Stringresp= responseBody.replaceAll("\n", " ");
System.out.println("RESPONSE \n" + resp);
}
} catch (Exception e) {
e.printStackTrace();
}
Solution 4:
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/Putint statusCode = this.requestImpl(requestMethod, headers, data);
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED && hasUserNameAndPassword) {
String auth = getResponseHeaderField("WWW-Authenticate");
// Server needs Digest autheticationif(auth.startsWith("Digest")){
// Parse the auth Header
HashMap<String, String> authFields = parseWWWAuthenticateHeader(auth);
// Generate Auth-Value for requestString 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:
privatestaticString 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.
Solution 5:
For Android, I found the bare-bones-digest library worked well: https://github.com/al-broco/bare-bones-digest
- Add one line to build.gradle
- Use the example code at the above url
Works!
Post a Comment for "Digest Authentication In Android Using Httpurlconnection"