正如问题所说,我正在尝试在android中进行摘要式身份验证
到目前为止,我已经使用了DefaultHttpClient
及其身份验证方法(使用UsernamePasswordCredentials
等),但自Android 5以来已弃用,将在Android 6中删除。
所以我即将从DefaultHttpClient
切换到HttpUrlConnection
现在我正在尝试实现摘要式身份验证,这应该非常简单,如here所述:
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
但getPasswordAuthentication
因某种原因从未被调用过
在我搜索这个问题的过程中,我找到了不同的帖子,说安卓认证不支持android中的HttpUrlConnection
,但这些帖子是从2010年到2012年,所以我不确定这是否仍然是真的。此外,我们在桌面java应用程序中使用HttpUrlConnection
进行摘要式身份验证,并在其中运行。
我还发现了一些帖子,谈论OkHttp
。 Android 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"
所以基本身份验证不应该工作,因为服务器要求摘要。
答案 0 :(得分:6)
答案是,HttpUrlConnection
不支持摘要。
因此,您必须自己实施RFC2617。
您可以使用以下代码作为基准实现:HTTP Digest Auth for Android。
步骤涉及(参见RFC2617以供参考):
WWW-Authenticate
标头并解析它们:
auth
qop选项),否则忽略挑战并转到下一个标题。Authenticator.requestPasswordAuthentication
获取凭据。Authorization
标题并将其添加到HttpUrlConnection
。使用Authenticator
,您可以确保HttpUrlConnection
本身支持摘要,您的代码不再被使用(因为您首先不会收到401)。< / p>
这只是一个关于如何实现它的快速摘要,供您了解。
如果你想更进一步,你可能也希望实现SHA256:RFC7616
答案 1 :(得分:4)
HttpUrlConnection
不支持摘要式身份验证是正确的。如果您的客户端必须使用Digest进行身份验证,您可以选择以下几种方法:
HttpURLConnection
之上使用。okhttp-digest
作为身份验证器,您就可以获得透明的Http摘要支持。如果您已经使用OkHttp或者切换到它可以,这可能是一个很有吸引力的选择。HttpClient
。问题明确指出HttpClient
不是一个选项,因此我将其包括在内是为了完成。 Google建议不要使用HttpClient
并弃用它。答案 2 :(得分:1)
您是否尝试手动设置标题,如:
String basic = "Basic " + new String(Base64.encode("username:password".getBytes(),Base64.NO_WRAP ));
connection.setRequestProperty ("Authorization", basic);
还要注意Jellybeans中的一些问题以及尝试执行帖子请求时的错误:HTTP Basic Authentication issue on Android Jelly Bean 4.1 using HttpURLConnection
编辑:用于摘要式身份验证
在这里查看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();
}
答案 3 :(得分:1)
我最终用我自己的DefaultHttpClient
实现替换了已弃用的HttpUrlConnection
,我自己实施了digest atuhentication
,使用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);
}
}
所以基本上我发出请求,如果它返回401,我看,如果服务器想要digest authentication
,并且我有用户名和密码。如果是这种情况,我解析响应的auth头,其中包含有关身份验证的所有必要信息
要解析auth标头,我使用某种StateMachine
来描述here
在解析响应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;
}
我希望这有助于某人。如果代码包含任何错误,请告诉我,以便我可以修复它。但是现在它似乎有效。
答案 4 :(得分:0)
对于Android,我发现裸机消化库效果很好:https://github.com/al-broco/bare-bones-digest
作品!