GeoIP2-java icon indicating copy to clipboard operation
GeoIP2-java copied to clipboard

How to set up proxy credentials?

Open marcioggs opened this issue 1 year ago • 4 comments

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.

marcioggs avatar Sep 11 '24 14:09 marcioggs

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

https://github.com/maxmind/GeoIP2-java/blob/cd77286b4b3e233df41bdfe927512f9c3452c45f/src/main/java/com/maxmind/geoip2/WebServiceClient.java#L143-L147

oschwald avatar Sep 12 '24 17:09 oschwald

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.

marcioggs avatar Sep 13 '24 07:09 marcioggs

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.

oschwald avatar Sep 13 '24 17:09 oschwald

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.

marcioggs avatar Sep 16 '24 07:09 marcioggs

Hi, is it possible to update your api to set an authenicator from the WebServiceClient.Builder in order to use proxy with with Basic authentication ? like : WebServiceClient.Builder(Integer.parseInt(accountId), licenceKey).host(hostname).proxyAuthenticator(Authenticator.getDefault()).build();

vgrassaud avatar Aug 13 '25 12:08 vgrassaud

Hi! Based on the above comments, I'm not sure this is possible currently. We'll look into how to do it and potentially make it possible.

horgh avatar Aug 13 '25 15:08 horgh

With the 4.4.0 release, we now allow setting the HttpClient that WebServiceClient uses. I believe that should resolve this issue.

oschwald avatar Sep 24 '25 20:09 oschwald