Apache HttpClient 摘要认证

15

我需要做的基本上是执行摘要认证。 我尝试的第一件事是使用这里提供的官方示例。但是当我尝试执行它时(进行了一些小更改,使用Post方法而不是Get方法),我遇到了一个

org.apache.http.auth.MalformedChallengeException: missing nonce in challange
at org.apache.http.impl.auth.DigestScheme.processChallenge(DigestScheme.java:132)

当这种方法失败后,我尝试使用:

DefaultHttpClient client = new DefaultHttpClient();
client.getCredentialsProvider().setCredentials(new AuthScope(null, -1, null), new UsernamePasswordCredentials("<username>", "<password>"));

HttpPost post = new HttpPost(URI.create("http://<someaddress>"));
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("domain", "<username>"));
post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

DigestScheme digestAuth = new DigestScheme();
digestAuth.overrideParamter("algorithm", "MD5");
digestAuth.overrideParamter("realm", "http://<someaddress>");
digestAuth.overrideParamter("nonce", Long.toString(new Random().nextLong(), 36));
digestAuth.overrideParamter("qop", "auth");
digestAuth.overrideParamter("nc", "0");
digestAuth.overrideParamter("cnonce", DigestScheme.createCnonce());

Header auth = digestAuth.authenticate(new
      UsernamePasswordCredentials("<username>", "<password>"), post);
System.out.println(auth.getName());
System.out.println(auth.getValue());
post.setHeader(auth);


HttpResponse ret = client.execute(post);
ByteArrayOutputStream v2 = new ByteArrayOutputStream();
ret.getEntity().writeTo(v2);
System.out.println("----------------------------------------");
System.out.println(v2.toString());
System.out.println("----------------------------------------");
System.out.println(ret.getStatusLine().getReasonPhrase());
System.out.println(ret.getStatusLine().getStatusCode());

一开始我只覆盖了“realm”和“nonce”DigestScheme参数。但后来发现运行在服务器上的PHP脚本需要所有其他参数,但无论我是否指定它们,DigestScheme都不会在调用其authenticate()方法时在Authorization RequestPreperty中生成它们。而PHP脚本返回HTTP响应代码200,并带有一条消息,指出PHP脚本需要cnonce、nc和qop参数。

我已经苦苦挣扎两天了,也没有什么进展。根据我所知道的一切,问题的根源是PHP脚本。我觉得它在未经授权的情况下尝试访问它时没有发送挑战(challenge)。

有任何想法吗?

编辑: 还有一件事,我已经尝试使用cURL连接,它可以正常工作。


看起来服务器正在发送非标准挑战。你能在浏览器中尝试并获取头部跟踪吗? - ZZ Coder
我已经使用HttpURLConnection连接到服务器并打印出响应头。以下是挑战的样子: 键 = www-authenticate 值 = Digest realm="REST API" qop="auth" nonce="4c063992df3dd" opaque="aba3d4b49c454e1974970e7b5514b001" - Milan
4个回答

5
这段代码对我很有用。您需要提供领域,可以通过查看从主机获得的401响应标头获取它。
val credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
  new UsernamePasswordCredentials(user, password));
val authCache = new BasicAuthCache();
val digestScheme = new DigestScheme();

digestScheme.overrideParamter("realm", "**Name of the Realm**");
// Nonce value
digestScheme.overrideParamter("nonce", "whatever");

authCache.put(targetHost, digestScheme);

context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);

val httpget = new HttpGet(url);

val response = httpClient.execute(targetHost, httpget, context);

这个答案非常有帮助,但它实际上是伪代码。这里有一个实际的工作示例,可以从服务器获取realmnonceopaque值:https://stackoverflow.com/questions/53028003/apache-httpclient-digestauth-doesnt-forward-opaque-value-from-challenge/53028597#53028597 - Alex R

3

我在验证代码后,使用digestScheme成功进行了摘要登录。

digestAuth.processChallenge(null);

强制解释之前的输入参数。null参数是一个头,基于已发送的头(如果有的话)。

现在使用 qop/nc 并且digestScheme按照需要工作。在Android上运行它。

digestAuth.overrideParamter("algorithm", "MD5");
digestAuth.overrideParamter("realm", serverRealm);
digestAuth.overrideParamter("nonce", Long.toString(new Random().nextLong(), 36));
digestAuth.overrideParamter("qop", "auth");//   not effective 
digestAuth.overrideParamter("nc",""+sequence);//nt effective 
digestAuth.overrideParamter("cnonce", DigestScheme.createCnonce());
digestAuth.overrideParamter("opaque","ba897c2f0f3de9c6f52d");
String err;
try
{
    digestAuth.processChallenge(null);
    //force  qop in use  chalange  on return header ????!!!!
}
catch (Exception e)
{ 
    err=e.getLocalizedMessage();
}

1
我如何通过编程确定领域?这段代码是为一个库而写的,它将在许多连接到许多服务器的计算机上运行。因此,我不能硬编码它。 - David Thielen
我该如何确定“opaque”?我尝试调用的服务似乎需要它,并且每个新会话都会更改。 - Alex R

2
private static byte[] downloadFileWithDigitAuth(String url, String username, String password) {
    byte[] bytes = null;
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    HttpGet httpGet = new HttpGet(url);
    HttpContext httpContext = new BasicHttpContext();
    CloseableHttpResponse httpResponse = null;
    try {
        httpResponse = httpClient.execute(httpGet, httpContext);

        if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
            Header authHeader = httpResponse.getFirstHeader(AUTH.WWW_AUTH);
            DigestScheme digestScheme = new DigestScheme();

            /*
            override values if need
            No need override values such as nonce, opaque, they are generated by server side
            */
            digestScheme.overrideParamter("realm", "User Login Required !!");
            digestScheme.processChallenge(authHeader);

            UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password);
            httpGet.addHeader(digestScheme.authenticate(creds, httpGet, httpContext));

            httpResponse.close();
            httpResponse = httpClient.execute(httpGet);
        }
        bytes = IOUtils.toByteArray(httpResponse.getEntity().getContent());
    } catch (IOException | MalformedChallengeException | AuthenticationException e) {
        e.printStackTrace();
    }
    finally {
        if (httpResponse != null) {
            try {
                httpResponse.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return bytes;
}

Gradle :

compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.12'
compile group: 'commons-io', name: 'commons-io', version: '2.6'

-1
你们把它弄得太复杂了。如果你阅读 Apache HttpClient 的文档,那就会变得非常容易。
protected static void downloadDigest(URL url, FileOutputStream fos)
    throws IOException {
    HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol());
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpClientContext context = HttpClientContext.create();

    String credential = url.getUserInfo();
    if (credential != null) {
        String user = credential.split(":")[0];
        String password = credential.split(":")[1];

        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(AuthScope.ANY,
            new UsernamePasswordCredentials(user, password));
        AuthCache authCache = new BasicAuthCache();
        DigestScheme digestScheme = new DigestScheme();
        authCache.put(targetHost, digestScheme);

        context.setCredentialsProvider(credsProvider);
        context.setAuthCache(authCache);
    }

    HttpGet httpget = new HttpGet(url.getPath());

    CloseableHttpResponse response = httpClient.execute(targetHost, httpget, context);

    try {
        ReadableByteChannel rbc = Channels.newChannel(response.getEntity().getContent());
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
    } finally {
        response.close();
    }
}

由于DigestScheme需要拉取WWW-Authenticate,因此您必须注册它,而不是返回缓存。缓存表明它是预先处理的,并且所有数据已经存在。 - Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接