使用HttpURLConnection

时间:2015-09-21 07:04:51

标签: java android httpurlconnection digest-authentication

正如问题所说,我正在尝试在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"

所以基本身份验证不应该工作,因为服务器要求摘要。

5 个答案:

答案 0 :(得分:6)

答案是,HttpUrlConnection不支持摘要。

因此,您必须自己实施RFC2617

您可以使用以下代码作为基准实现:HTTP Digest Auth for Android

步骤涉及(参见RFC2617以供参考):

  • 如果您收到401响应,请遍历所有WWW-Authenticate标头并解析它们:
    • 检查算法是MD5还是未定义,(可选择auth qop选项),否则忽略挑战并转到下一个标题。
    • 使用Authenticator.requestPasswordAuthentication获取凭据。
    • 使用用户名,领域和密码计算H(A1)。
    • 存储规范的根URL,域,HA1,用户名,nonce(+可选算法,opaque和客户端选择的qop选项,如果存在)。
    • 重试请求。
  • 在每个请求上,迭代您拥有规范根URL存储的会话信息的所有领域:
    • 使用请求方法和路径计算H(A2)。
    • 使用HA1,nonce(+可选nc,cnonce,qop)和HA2计算H(A3)。
    • 构建Authorization标题并将其添加到HttpUrlConnection
  • 实施某种会话修剪。

使用Authenticator,您可以确保HttpUrlConnection本身支持摘要,您的代码不再被使用(因为您首先不会收到401)。< / p>

这只是一个关于如何实现它的快速摘要,供您了解。

如果你想更进一步,你可能也希望实现SHA256:RFC7616

答案 1 :(得分:4)

HttpUrlConnection不支持摘要式身份验证是正确的。如果您的客户端必须使用Digest进行身份验证,您可以选择以下几种方法:

  • 编写自己的HTTP摘要实现。如果您知道需要对哪些服务器进行身份验证,并且可以忽略您不需要的摘要规范部分,那么这可能是一个不错的选择。以下是实施摘要子集的示例:https://gist.github.com/slightfoot/5624590
  • 使用外部库bare-bones-digest,这是Android的摘要库。您可以使用它来解析摘要挑战并生成对它们的响应。它支持常见的摘要用例和一些很少使用的用例,可以在HttpURLConnection之上使用。
  • OkHttpokhttp-digest一起使用,{{3}}是一个为O​​kHttp添加Http Digest支持的插件。使用OkHttp支持摘要很简单,只需添加okhttp-digest作为身份验证器,您就可以获得透明的Http摘要支持。如果您已经使用OkHttp或者切换到它可以,这可能是一个很有吸引力的选择。
  • 使用支持摘要的Apache 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

  1. 在build.gradle中添加一行
  2. 使用上述网址
  3. 中的示例代码

    作品!