[英]Digest authentication in Android using HttpURLConnection
正如問題所說,我正在嘗試在android中進行摘要式身份驗證。
到目前為止,我已經使用了DefaultHttpClient
及其身份驗證方法(使用UsernamePasswordCredentials
等),但自Android 5以來已棄用,並將在Android 6中刪除。
所以我即將從DefaultHttpClient
切換到HttpUrlConnection
。
我現在想實現摘要式身份驗證,這應該工作作為解釋非常簡單的在這里 :
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
但getPasswordAuthentication
從未因某種原因被調用。
在我搜索這個問題的過程中,我發現了不同的帖子,說Android中的HttpUrlConnection
不支持摘要認證,但這些帖子是從2010年到2012年,所以我不確定這是否仍然是真的。 此外,我們在桌面java應用程序中使用HttpUrlConnection
和摘要式身份驗證,它可以在其中運行。
我還發現了一些帖子,談論OkHttp
。 OkHttp
似乎被Android引用(更具體地說是HttpUrlConnectionImpl
)。 但是這個HttpUrlConnectionImpl
有點奇怪,甚至沒有在Eclipse類型層次結構中顯示,我也無法調試它。 它也應該是一個com.squareup.okhttp.internal.huc.HttpUrlConnectionImpl
,而在android中它是一個com.android.okhttp.internal.http.HttpUrlConnectionImpl
。
所以我只是無法在Android中使用此HttpUrlConnection
進行摘要式身份驗證。
沒有外部庫,誰能告訴我如何做到這一點?
編輯:
服務器請求摘要式身份驗證:
WWW-Authenticate: Digest realm="Realm Name",domain="/domain",nonce="nonce",algorithm=MD5,qop="auth"
所以基本身份驗證不應該工作,因為服務器要求摘要。
答案是, HttpUrlConnection
不支持摘要。
因此,您必須自己實施RFC2617 。
您可以使用以下代碼作為基線實現: Android的HTTP Digest Auth 。
涉及的步驟(參見RFC2617以供參考):
WWW-Authenticate
標頭並解析它們:
auth
qop選項),否則忽略挑戰並轉到下一個標題。 Authenticator.requestPasswordAuthentication
獲取憑據。 HttpUrlConnection
添加Authorization
標頭。 通過使用Authenticator
,您可以確保,只要HttpUrlConnection
本身支持摘要,您的代碼就不再被使用(因為您首先不會收到401)。
這只是關於如何實現它的快速摘要,供您了解。
如果你想更進一步,你可能也希望實現SHA256: RFC7616
HttpUrlConnection
不支持摘要式身份驗證是正確的。 如果您的客戶端必須使用摘要進行身份驗證,您有以下幾種選擇:
HttpURLConnection
之上使用。 okhttp-digest
作為身份驗證器,您就okhttp-digest
透明的Http摘要支持。 如果您已經使用OkHttp或者切換到它可以,這可能是一個很有吸引力的選擇。 HttpClient
。 問題明確指出HttpClient
不是一個選項,所以我主要是為了完成而包含它。 Google不建議使用HttpClient
並且已棄用它。 您是否嘗試手動設置標題,如:
String basic = "Basic " + new String(Base64.encode("username:password".getBytes(),Base64.NO_WRAP ));
connection.setRequestProperty ("Authorization", basic);
還要注意Jellybeans中的一些問題以及嘗試執行發布請求時的錯誤: 使用HttpURLConnection在Android Jelly Bean 4.1上的HTTP基本身份驗證問題
編輯: 用於摘要式身份驗證
請查看https://code.google.com/p/android/issues/detail?id=9579
特別是這可能有效:
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();
}
我最后用我自己的HttpUrlConnection
實現替換了已棄用的DefaultHttpClient
並且我自己實現了digest atuhentication
,使用它作為模板。
最終代碼看起來像這樣:
// 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);
}
}
所以基本上我發出請求,如果它返回401,我看,如果服務器想要digest authentication
,如果我有用戶名和密碼。 如果是這種情況,我解析響應的auth頭,其中包含有關身份驗證的所有必要信息。
要解析auth標頭,我使用某種StateMachine
, 這里描述。
在解析響應auth頭之后,我使用響應中的信息生成請求auth頭:
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();
}
要生成Client-Nonce,我使用以下代碼:
private static String generateCNonce() {
String s = "";
for (int i = 0; i < 8; i++)
s += Integer.toHexString(new Random().nextInt(16));
return s;
}
我希望這可以幫助別人。 如果代碼包含任何錯誤,請告訴我,以便我可以修復它。 但是現在它似乎有效。
對於Android,我發現裸機消化庫運行良好: https : //github.com/al-broco/bare-bones-digest
作品!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.