Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set up proxy credentials? #478

Open
marcioggs opened this issue Sep 11, 2024 · 4 comments
Open

How to set up proxy credentials? #478

marcioggs opened this issue Sep 11, 2024 · 4 comments

Comments

@marcioggs
Copy link

marcioggs commented Sep 11, 2024

Hi.

I'm using the library to call but the company I work for has a proxy that requires credentials.
How do I set up the credentials?

I already set up the default ProxySelector, which the library is using:

ProxySelector.setDefault(ProxySelector.of(new InetSocketAddress("host", 9999)));

I also set up the default Authenticator, but its method is not executed when I try to interact with the API using the library methods, which results in HTTP error 407 Proxy Authentication Required:

    Authenticator.setDefault(
        new Authenticator() {
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            if (getRequestorType() == RequestorType.PROXY) {
              return new PasswordAuthentication("proxyUsername", "proxyPassword".toCharArray());
            }
            return null;
          }
        });

Thanks for your attention.

@oschwald
Copy link
Member

Although I have not done it myself, I would have expected that to work. You can see how we are creating the HttpClient here:

httpClient = HttpClient.newBuilder()
.connectTimeout(builder.connectTimeout)
.proxy(builder.proxy)
.build();
}

@marcioggs
Copy link
Author

marcioggs commented Sep 13, 2024

Hi @oschwald .
Thanks for the info.

The only way I could make the proxy correctly authenticated was by changing WebServiceClient to receive the authenticator in its Builder:

Authenticator authenticator = new Authenticator() {
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            if (getRequestorType() == RequestorType.PROXY) {
              return new PasswordAuthentication("proxyUsername", "proxyPassword".toCharArray());
            }
            return null;
          }
        }
        
new WebServiceClient.Builder(42, "license_key")
        .authenticator(authenticator)
        .build();

However, because the authenticator returns null for non-proxy requests, the API call has the authentication header set by WebServiceClient cleared, resulting in an API authentication error.

The version below works with the API credentials duplicated by the authenticator.

Authenticator authenticator = new Authenticator() {
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            if (getRequestorType() == RequestorType.PROXY) {
              return new PasswordAuthentication("proxyUsername", "proxyPassword".toCharArray());
            }
            return PasswordAuthentication(42, "license_key".toCharArray());;
          }
        }
        
new WebServiceClient.Builder(42, "license_key")
        .authenticator(authenticator)
        .build();

The Javadoc for Authenticatior#setDefault says that the default authenticator is used if the "HTTP server asks for authentication", and I think that the MaxMind API server is not asking for HTTP authentication, so that's why the default authenticator should have been ignored.

Open this URL below for example in the browser and it will pop up the authentication form because the server returns the www-authenticate header:
https://authenticationtest.com/HTTPAuth/

The MaxMind API server doesn't return this header, making the web browser not to request credentials:
https://geoip.maxmind.com/geoip/v2.1/country/me

See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate

If the API would be changed to return this header it can still make sense to change its Java client library so that the API credentials do not have to be sent in two places.

I changed the application I'm working on to use the REST API instead of the Java library so that it has better control over the proxy and API credentials being sent.

@oschwald
Copy link
Member

The WWW-Authenticate header is set in most cases. Your browser test is hitting a path for the JS auth method.

For instance, a request with curl:

$ curl -v https://geoip.maxmind.com/geoip/v2.1/city/1.1.1.1
* Host geoip.maxmind.com:443 was resolved.
* IPv6: 2606:4700:7::a29f:8716, 2606:4700:7::a29f:8616
* IPv4: 162.159.134.22, 162.159.135.22
*   Trying 162.159.134.22:443...
* Connected to geoip.maxmind.com (162.159.134.22) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=maxmind.com
*  start date: Aug 10 04:48:49 2024 GMT
*  expire date: Nov  8 04:48:48 2024 GMT
*  subjectAltName: host "geoip.maxmind.com" matched cert's "*.maxmind.com"
*  issuer: C=US; O=Google Trust Services; CN=WE1
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 2: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using ecdsa-with-SHA384
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://geoip.maxmind.com/geoip/v2.1/city/1.1.1.1
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: geoip.maxmind.com]
* [HTTP/2] [1] [:path: /geoip/v2.1/city/1.1.1.1]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET /geoip/v2.1/city/1.1.1.1 HTTP/2
> Host: geoip.maxmind.com
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/2 401
< date: Fri, 13 Sep 2024 17:06:27 GMT
< content-type: application/vnd.maxmind.com-error+json; charset=UTF-8; version=2.1
< content-length: 106
< www-authenticate: Basic realm="geoip2"
< cf-cache-status: DYNAMIC
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< server: cloudflare
< cf-ray: 8c29ba5bac500933-SEA
<
* Connection #0 to host geoip.maxmind.com left intact
{"code":"AUTHORIZATION_INVALID","error":"An account ID and license key are required to use this service."}

That said, I don't think we would want the client to issue a request that returned the header on a 401 and then have to make another round trip with the Authorization header.

@marcioggs
Copy link
Author

Thanks @oschwald .

Indeed, the specific endpoint (https://geoip.maxmind.com/geoip/v2.1/country/{ip}) that I was trying to call with the client library is returning the header, so I don't know why the default authenticator is not taken into consideration by the library and I'm not sure there's a way to authenticate the proxy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants