簡體   English   中英

使用HttpURLConnection在Android中進行摘要式身份驗證

[英]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以供參考):

  • 如果您收到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)。
    • 構建並向您的HttpUrlConnection添加Authorization標頭。
  • 實現某種會話修剪。

通過使用Authenticator ,您可以確保,只要HttpUrlConnection本身支持摘要,您的代碼就不再被使用(因為您首先不會收到401)。

這只是關於如何實現它的快速摘要,供您了解。

如果你想更進一步,你可能也希望實現SHA256: RFC7616

HttpUrlConnection不支持摘要式身份驗證是正確的。 如果您的客戶端必須使用摘要進行身份驗證,您有以下幾種選擇:

  • 編寫自己的HTTP Digest實現。 如果您知道需要對哪些服務器進行身份驗證,並且可以忽略您不需要的摘要規范部分,那么這可能是一個不錯的選擇。 下面是一個實現摘要子集的示例: https//gist.github.com/slightfoot/5624590
  • 使用外部lib bare-bones-digest ,它是Android的摘要庫。 您可以使用它來解析摘要挑戰並生成對它們的響應。 它支持常見的摘要用例和一些很少使用的用例,可以在HttpURLConnection之上使用。
  • OkHttpokhttp-digest一起使用,這是一個為O​​kHttp添加Http Digest支持的插件。 使用OkHttp支持摘要很簡單,只需添加okhttp-digest作為身份驗證器,您就okhttp-digest透明的Http摘要支持。 如果您已經使用OkHttp或者切換到它可以,這可能是一個很有吸引力的選擇。
  • 使用支持Digest的Apache 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

  1. 在build.gradle中添加一行
  2. 使用上面網址中的示例代碼

作品!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM