【问题标题】:Need help creating Digest authentication for Android需要帮助为 Android 创建摘要式身份验证
【发布时间】:2012-03-18 01:55:20
【问题描述】:

到目前为止我有这个代码:

private class DownloadWebPageTask extends AsyncTask<String, Void, String> 
{
        @Override
        protected String doInBackground(String... theParams) 
        {
            String myUrl = theParams[0];
            String myEmail = theParams[1];
            String myPassword = theParams[2];

            HttpPost post = new HttpPost(myUrl);
            post.addHeader("Authorization","Basic "+ Base64.encodeToString((myEmail+":"+myPassword).getBytes(), 0 ));
            ResponseHandler<String> responseHandler = new BasicResponseHandler();

            String response = null;

            try 
            {
                    response = client.execute(post, responseHandler);
                InputStream content = execute.getEntity().getContent();

                BufferedReader buffer = new BufferedReader(
                            new InputStreamReader(content));
                    String s = "";
                    while ((s = buffer.readLine()) != null) 
                    {
                        response += s;
                    }
                } 
                catch (Exception e) 
                {
                    e.printStackTrace();
                }

            return response;
        }


        @Override
        protected void onPostExecute(String result) 
        {
            }

}

此代码无法编译,因为我在以下位置遇到了困惑:

                response = client.execute(post, responseHandler);
                InputStream content = execute.getEntity().getContent();

我通过修改各种示例获得了该代码,但不确定客户端应该是什么对象,以及第一行是否只会让我得到服务器响应,或者我必须走获取 InputStream 的路线和读取服务器响应?

请帮助我了解如何正确执行此操作。

谢谢!

【问题讨论】:

  • 1. client 是 HttpClient 2. client.execute(...) 返回 HttpResponse,而不是 String 3. InputStream content = response.getEntity().getContent() (response mean HttpResponse);
  • @appserv 我很困惑什么是客户端对象 :) 如何获取客户端对象? :)
  • HttpClient 客户端 = new DefaultHttpClient();

标签: android android-asynctask digest-authentication


【解决方案1】:

Android 附带的 Apache 的 HttpClient 版本是 based on an old, pre-BETA version of HttpClient。谷歌有long recommended against using itremoved it in Android 6.0。谷歌的替换HttpURLConnectiondoes not support HTTP digest authentication,只是基本的。

这给您留下了几个选择,包括:

  • 迁移到 HttpURLConnection(按照 Google 的建议)并使用库 bare-bones-digest 进行摘要式身份验证。示例如下。
  • 使用OkHttp 库而不是HttpURLConnectionHttpClient。 OkHttp 不支持开箱即用的摘要,但有一个库okhttp-digest 实现了摘要身份验证器。示例如下。
  • changelist for Android 6.0 中所述,通过将'org.apache.http.legacy' 库显式添加到您的构建中,继续使用(已弃用)HttpClient
  • 有一个 Apache 项目用于将较新版本的 HttpClient 移植到 Android,但该项目已停止。在Apache's page on HttpClient for Android 上阅读更多信息。
  • 自己实现 HTTP 摘要。

这是一个详细示例,说明如何使用 bare-bones-digestHttpURLConnection 对请求进行身份验证(复制自项目的 github 页面):

// Step 1. Create the connection
URL url = new URL("http://httpbin.org/digest-auth/auth/user/passwd");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Step 2. Make the request and check to see if the response contains
// an authorization challenge
if (connection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    // Step 3. Create a authentication object from the challenge...
    DigestAuthentication auth = DigestAuthentication.fromResponse(connection);
    // ...with correct credentials
    auth.username("user").password("passwd");

    // Step 4 (Optional). Check if the challenge was a digest
    // challenge of a supported type
    if (!auth.canRespond()) {
        // No digest challenge or a challenge of an unsupported
        // type - do something else or fail
        return;
    }

    // Step 5. Create a new connection, identical to the original
    // one..
    connection = (HttpURLConnection) url.openConnection();
    // ...and set the Authorization header on the request, with the
    // challenge response
    connection.setRequestProperty(
        DigestChallengeResponse.HTTP_HEADER_AUTHORIZATION,
        auth.getAuthorizationForRequest("GET", connection.getURL().getPath()));
}

这是一个使用OkHttpokhttp-digest 的示例(从okhttp-digest 页面复制):

client = new OkHttpClient();
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials("username", "pass"));

final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
client.interceptors().add(new AuthenticationCacheInterceptor(authCache));
client.setAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache));

Request request = new Request.Builder()
  .url(url);
  .get()
  .build();
Response response = client.newCall(request).execute();

【讨论】:

    【解决方案2】:

    我已经设法通过OkHttp 使用摘要式身份验证。在这个代码示例中,我还使用了 Dagger 和 Robospice-retrofit。我所做的是创建一个OkHttp Authenticator 并将其分配给我的自定义 OkHttp 客户端。

    authenticator 类实现了一个 authenticate 方法,只要服务器遇到 401 错误并期望返回 Authorization 标头(如果它期望 Proxy-授权你应该实现authenticateProxy方法。

    它主要做的是包装对 HttpClient DigestScheme 的调用,并使其可用于 OkHttp。目前它不会增加 nc 计数器。这可能会导致您的服务器出现问题,因为它可能被解释为重放攻击。

    public class DigestAuthenticator implements com.squareup.okhttp.Authenticator {
        @Inject DigestScheme mDigestScheme;
        @Inject org.apache.http.auth.Credentials mCredentials;
    
        @Override
        public Request authenticate(Proxy proxy, Response response) throws IOException {
            String authHeader = buildAuthorizationHeader(response);
            if (authHeader == null) {
                return null;
            }
            return response.request().newBuilder().addHeader("Authorization", authHeader).build();
        }
    
        @Override
        public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
            return null;
        }
    
        private String buildAuthorizationHeader(Response response) throws IOException {
            processChallenge("WWW-Authenticate", response.header("WWW-Authenticate"));
            return generateDigestHeader(response);
        }
    
        private void processChallenge(String headerName, String headerValue) {
            try {
                mDigestScheme.processChallenge(new BasicHeader(headerName, headerValue));
            } catch (MalformedChallengeException e) {
                Timber.e(e, "Error processing header " + headerName + " for DIGEST authentication.");
            }
        }
    
        private String generateDigestHeader(Response response) throws IOException {
            org.apache.http.HttpRequest request = new BasicHttpRequest(
                    response.request().method(),
                    response.request().uri().toString()
            );
    
            try {
                return mDigestScheme.authenticate(mCredentials, request).getValue();
            } catch (AuthenticationException e) {
                Timber.e(e, "Error generating DIGEST auth header.");
                return null;
            }
        }
    }
    

    验证器随后将用于使用提供程序构建的 OkHttpClient:

    public class CustomClientProvider implements Client.Provider {
        @Inject DigestAuthenticator mDigestAuthenticator;
    
        @Override
        public Client get() {
            OkHttpClient client = new OkHttpClient();
            client.setAuthenticator(mDigestAuthenticator);
            return new OkClient(client);
        }
    }
    

    最后在函数createRestAdapterBuilder中将客户端设置为RetrofitRobospice服务器:

    public class ApiRetrofitSpiceService extends RetrofitJackson2SpiceService {
        @Inject Client.Provider mClientProvider;
    
        @Override
        public void onCreate() {
            App.get(this).inject(this);
            super.onCreate();
            addRetrofitInterface(NotificationRestInterface.class);
        }
    
        @Override
        protected String getServerUrl() {
            return Constants.Url.BASE;
        }
    
        @Override
        protected RestAdapter.Builder createRestAdapterBuilder() {
            return super.createRestAdapterBuilder()
                    .setClient(mClientProvider.get());
        }
    }
    

    【讨论】:

    • 奇怪,使用你的技术我总是收到 400 个错误的请求——不过我没有使用 RetrofitRobospice——你认为这是否相关?我不明白它是怎么回事,但我没有使用 robospice 的经验
    • 你还在使用 Retrofit 吗?你能发布整个请求和响应吗?
    • 是的,我使用 Retrofit。在身份验证的返回语句中生成的请求只有 2 个标头,它们在这里(和响应也是):pastebin.com/QV8hm8Kq
    • 请求是 GET 或 POST。我无法弄清楚你的要求有什么问题。您可以查看正确生成请求的 HTTP Digest 身份验证的 RFC 规范。您还可以检查服务器是否配置正确。如果您发现问题,请告诉我们,我会更新我的答案。
    • 它是 GET。服务器似乎没问题,因为它正确响应了使用 CURL 发出的请求。
    【解决方案3】:

    您可能想切换到HttpURLConnection。根据this article 的说法,它的API 比HttpClient 的更简单,并且在Android 上得到更好的支持。如果您确实选择使用HttpURLConnection,则身份验证非常简单:

    Authenticator.setDefault(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("username", "password".toCharArray());
        }
    });
    

    之后,像往常一样继续使用HttpURLConnection。一个简单的例子:

    final URL url = new URL("http://example.com/");
    final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    final InputStream is = conn.getInputStream();
    final byte[] buffer = new byte[8196];
    int readCount;
    final StringBuilder builder = new StringBuilder();
    while ((readCount = is.read(buffer)) > -1) {
        builder.append(new String(buffer, 0, readCount));
    }
    final String response = builder.toString();
    

    【讨论】:

    • 切换到HttpUrlConnection有什么好处?此外,您建议的方法是否仍符合 Android Digest Authentication 方法?谢谢!
    • @GeekedOut 链接的文章中概述了切换到HttpURLConnection 的好处。至于摘要身份验证支持,是的,Authenticator 方法支持 HTTP Basic、HTTP Digest、NTLM 和 SPNEGO 身份验证,如here 所述。它根据服务器的响应选择适当的身份验证方法。
    • 但是您提供的链接讨论了其他事情。我很困惑在哪里放置以及如何调用 Authenticator sn-p。它应该在扩展 AsyncTask 的内部类中吗?还是外面?它是隐式调用还是显式调用?谢谢!
    • 我提供的链接讨论了 HttpURLConnection 与 Apache 的 HttpClient 以及前者在 Android 上的优势。至于代码,我不知道您所说的“隐式或显式”是什么意思。为简单起见,您可以在AsyncTask 的一个大doInBackground 方法中简单地将我的答案中的两位代码组合起来。然而,既然你问了“这段代码在哪里”的问题,我想说你应该更多地学习 Java,因为这不是 Android 特定的问题。
    • @Felix - 您提到链接的文章适用于 NTLM 和 SPNEGO 等。你让它特别适用于那些人吗?我能够让基本工作。但是对于 NTLM/SPNEGO,永远不会调用 getPasswordAuthentication()。您是否需要执行任何额外的步骤?
    猜你喜欢
    • 2022-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-01
    • 2020-07-25
    • 2013-08-29
    相关资源
    最近更新 更多