如何针对单个请求阻止Apache HttpClient三次请求凭据?

时间:2015-04-27 02:57:58

标签: java http apache-httpclient-4.x

我使用Apache HttpClient连接到运行Jetty并使用WAFFLE进行身份验证的Web服务器。我遇到问题,服务器发送三个WWW-Authenticate标头(因为它支持三种身份验证方案),但随后在客户端上,CredentialsProvider被要求三次获得相同的凭据。< / p>

如果我使用内置的Java URL类连接到同一台服务器,它会获取资源而不会要求我提供凭据。

HTTP客户端设置非常简单:

RequestConfig requestConfig =
    HttpClientUtils.createDefaultRequestConfig();
HttpClientBuilder builder = HttpClients.custom()
    .setDefaultCredentialsProvider(credentialsProvider)
    .setDefaultCookieStore(cookieStore)
    .setDefaultRequestConfig(requestConfig);
return builder.build();

CredentialsProvider是一个自定义的,它会委托我们自己的框架提示用户提供该信息。

public class AdaptedCredentialsProvider
    implements CredentialsProvider
{
    private final CredentialsProvider internal =
        new BasicCredentialsProvider();
        //I tried this too:
        //new SystemDefaultCredentialsProvider();

    private final CredentialsPrompt prompt;

    public AdaptedCredentialsProvider(CredentialsPrompt prompt) {
        this.prompt = prompt;
    }

    @Override
    public void setCredentials(AuthScope authscope,
                               Credentials credentials) {
        internal.setCredentials(authscope, credentials);
    }

    @Override
    public Credentials getCredentials(@NotNull AuthScope authScope) {
        System.err.println("Asked for credentials, scheme: " +
                           authScope.getScheme());

        Credentials localCredentials =
            internal.getCredentials(authScope);
        if (localCredentials != null) {
            return localCredentials;
        }

        if (authScope.getHost() != null) {
            OurCredentials credentials = prompt.prompt(
                authScope.getHost(),
                authScope.getPort());

            if (credentials != null) {
                switch (authScope.getScheme()) {
                    case "NEGOTIATE":
                    case "NTLM":
                        return new NTCredentials(
                            credentials.getUsername(),
                            new String(credentials.getPassword()),
                            null, null);

                    default:
                        return new UsernamePasswordCredentials(
                            credentials.getUsername(),
                            new String(credentials.getPassword()));
                }
            }
        }

        return null;
    }

    @Override
    public void clear() {
        internal.clear();
    }
}

请求和响应的时间表以及散布的登录请求很有意思......

=== Initial request
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:23 GMT
Set-Cookie: JSESSIONID=1lsrefm3yzmmz15n8md7efdc7f;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289

=== Asked for credentials for host: 192.168.1.162 - scheme: NEGOTIATE

=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:30 GMT
Set-Cookie: JSESSIONID=ucsnvsvawp301injh6ijwbywe;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:30 GMT
Set-Cookie: JSESSIONID=1la5wb3fje1fy1qxu14weqxaqm;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0

=== Asked for credentials for host: 192.168.1.162 - scheme: NTLM

=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:33 GMT
Set-Cookie: JSESSIONID=5pmtxxhinky91bzoj18yx6zkz;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:33 GMT
Set-Cookie: JSESSIONID=16jhmw3fxuhsy1edjtpquiw564;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0

=== Asked for credentials for host: 192.168.1.162 - scheme: BASIC

=== GUI checking whether the credentials work...
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate    
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=2yjw3xcoerq5wdtqu1etxur0;Path=/;Secure
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="Test"
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 289
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: Basic <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=cvf6mvdagknk1mb893u7ylozb;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0

=== Presumably here it has decided it doesn't support NEGOTIATE so
=== it's going with NTLM.
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: NTLM <REDACTED>
=== Response
HTTP/1.1 401 Unauthorized
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=es21dv6pnktmqoxr9qs52n3e;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
WWW-Authenticate: NTLM <REDACTED>
Transfer-Encoding: chunked

0
=== Request
GET /api/noop HTTP/1.1
Host: 192.168.1.162:27443
Connection: Keep-Alive
Accept-Encoding: gzip,deflate
Authorization: NTLM <REDACTED>
=== Response
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2015 02:44:36 GMT
Set-Cookie: JSESSIONID=1nzdiyw90zun218drgkobi3y6g;Path=/;Secure
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 0

似乎它甚至没有使用它所要求的凭据。最终它以某种方式突破了这个奇怪的循环,然后尝试使用它们。

它最终会登录,但我怎么能阻止它三次询问相同的信息? (我知道我可以在CredentialsProvider中添加这类内容,但我没有必要使用其他API来实现这一点,这对于解决这个问题似乎是一种尴尬的方法。)< / p>

事实上,我怎么能阻止它要求提供这些信息呢?首先,使用Negotiate主要是为了避免被提示输入密码,但这件事总是提示我输入密码而且我认为不应该这样。

更新:问题的一半已经解决。我发现HttpClient 根本没有支持单点登录(有点误导,IMO,声称你支持NTLM而不支持使用NTLM的唯一目的!)但是版本4.4有实验支持它。

实验性支持似乎让我登录但仍然会提示我输入Basic的密码。所以我回到了如何禁用向凭证提供程序请求每个auth方案的这种行为的问题。理想情况下,它应该只是要求第一个,然后再回到序列中的下一个。

1 个答案:

答案 0 :(得分:0)

首先,您应该缓存凭据而不是向用户询问N次。您可以使用具有超时的缓存来提高安全性,并避免将密码保留在内存中太长时间。

您还必须确保为每个请求使用相同的credentialsProvidercookieStore

我不确定为什么Java URL可以连接。在代码中查找Authenticator.setDefault();没有它,Java的URL类不能进行任何身份验证。

[编辑] 您的代码每次都要求输入密码的原因是因为您这样写了:

  • 当服务器要求提供凭据时,您的代码中没有路径不会要求输入密码
  • 您没有缓存用户名和密码,所以每次都要再次询问
  • Oracle Java运行时可以创建至少NTLM的凭据,而无需输入密码。试着调查它是如何做到的。

相关: