eclipse-ee4j / eclipse-ee4j/jersey

Make it possible to control the Digest Authentication cacheKey to consider the domain attribute of the response header

Open
#5,395 0 comments 0 reactions 0 assignees View on GitHub
Type: Improvement
Dominant language
Java
Stars
730
Forks
382
PR merge metrics
No merged PRs in 30d

Description

## Motivation

We are migrating from jersey client 1.19 to 2.40 and we noticed that the Http Digest authentication behaves differently than in 1.19.

## Steps to reproduce

1. Setup your client like this:

```
HttpAuthenticationFeature authFeature = HttpAuthenticationFeature.universal(this.apiUsername, this.apiKey);
config.register(authFeature);
```

or

```
HttpAuthenticationFeature authFeature = HttpAuthenticationFeature.digest(this.apiUsername, this.apiKey);
config.register(authFeature);
```

2. Do at least two requests like this:
GET https://example.com:8080/api/articles/1
GET https://example.com:8080/api/articles/2

## Expected behavior (as of Jersey Client 1.19)

```
2023-08-25 10:09:24,023 18 * Sending client request on thread pool-9-thread-5
18 > GET http://example.com:8080/api/articles/1
18 > Accept: application/json,application/json
18 > User-Agent: My App Client

2023-08-25 10:09:24,091 18 * Client response received on thread pool-9-thread-5
18 < 401
18 < Cache-Control: nocache, private
18 < Connection: keep-alive
18 < Content-Type: application/json
18 < Date: Fri, 25 Aug 2023 08:09:24 GMT
18 < Server: nginx/1.18.0
18 < Transfer-Encoding: chunked
18 < Www-Authenticate: Digest realm="Shopware REST-API", domain="/", nonce="db57680d872101ef1f94aacf8e50e9c4", opaque="d75db7b160fe72d1346d2bd1f67bfd10", algorithm="MD5", qop="auth"
{"success":false,"message":"Invalid or missing auth"}

2023-08-25 10:09:24,092 19 * Sending client request on thread pool-9-thread-5
19 > GET http://example.com:8080/api/articles/1
19 > Accept: application/json,application/json
19 > Authorization: [redacted]
19 > User-Agent: My App Client

2023-08-25 10:09:24,218 19 * Client response received on thread pool-9-thread-5
19 < 200
19 < Cache-Control: nocache, private
19 < Connection: keep-alive
19 < Content-Type: application/json
19 < Date: Fri, 25 Aug 2023 08:09:24 GMT
19 < Server: nginx/1.18.0
19 < Transfer-Encoding: chunked
{"data":"foobar1"}

2023-08-25 10:09:24,234 20 * Sending client request on thread pool-9-thread-5
20 > GET http://example.com:8080/api/articles/2
20 > Accept: application/json,application/json
20 > Authorization: [redacted]
20 > User-Agent: My App Client

2023-08-25 10:09:24,306 21 * Sending client request on thread pool-9-thread-5
21 > GET http://example.com:8080/api/articles/2
21 > Accept: application/json,application/json
21 > Authorization: [redacted]
21 > User-Agent: My App Client

2023-08-25 10:09:24,434 21 * Client response received on thread pool-9-thread-5
21 < 200
21 < Cache-Control: nocache, private
21 < Connection: keep-alive
21 < Content-Type: application/json
21 < Date: Fri, 25 Aug 2023 08:09:24 GMT
21 < Server: nginx/1.18.0
21 < Transfer-Encoding: chunked
{"data":"foobar2"}
```

Notice just one 401 response.

## Actual Result (in Jersey client 2.x / 2.40)

```
2023-08-25 10:09:24,023 18 * Sending client request on thread pool-9-thread-5
18 > GET http://example.com:8080/api/articles/1
18 > Accept: application/json,application/json
18 > User-Agent: My App Client

2023-08-25 10:09:24,091 18 * Client response received on thread pool-9-thread-5
18 < 401
18 < Cache-Control: nocache, private
18 < Connection: keep-alive
18 < Content-Type: application/json
18 < Date: Fri, 25 Aug 2023 08:09:24 GMT
18 < Server: nginx/1.18.0
18 < Transfer-Encoding: chunked
18 < Www-Authenticate: Digest realm="Shopware REST-API", domain="/", nonce="db57680d872101ef1f94aacf8e50e9c4", opaque="d75db7b160fe72d1346d2bd1f67bfd10", algorithm="MD5", qop="auth"
{"success":false,"message":"Invalid or missing auth"}

2023-08-25 10:09:24,092 19 * Sending client request on thread pool-9-thread-5
19 > GET http://example.com:8080/api/articles/1
19 > Accept: application/json,application/json
19 > Authorization: [redacted]
19 > User-Agent: My App Client

2023-08-25 10:09:24,218 19 * Client response received on thread pool-9-thread-5
19 < 200
19 < Cache-Control: nocache, private
19 < Connection: keep-alive
19 < Content-Type: application/json
19 < Date: Fri, 25 Aug 2023 08:09:24 GMT
19 < Server: nginx/1.18.0
19 < Transfer-Encoding: chunked
{"data":"foobar1"}

2023-08-25 10:09:24,234 20 * Sending client request on thread pool-9-thread-5
20 > GET http://example.com:8080/api/articles/2
20 > Accept: application/json,application/json
20 > User-Agent: My App Client

2023-08-25 10:09:24,304 20 * Client response received on thread pool-9-thread-5
20 < 401
20 < Cache-Control: nocache, private
20 < Connection: keep-alive
20 < Content-Type: application/json
20 < Date: Fri, 25 Aug 2023 08:09:24 GMT
20 < Server: nginx/1.18.0
20 < Transfer-Encoding: chunked
20 < Www-Authenticate: Digest realm="Shopware REST-API", domain="/", nonce="db57680d872101ef1f94aacf8e50e9c4", opaque="d75db7b160fe72d1346d2bd1f67bfd10", algorithm="MD5", qop="auth"
{"success":false,"message":"Invalid or missing auth"}

2023-08-25 10:09:24,306 21 * Sending client request on thread pool-9-thread-5
21 > GET http://example.com:8080/api/articles/2
21 > Accept: application/json,application/json
21 > Authorization: [redacted]
21 > User-Agent: My App Client

2023-08-25 10:09:24,434 21 * Client response received on thread pool-9-thread-5
21 < 200
21 < Cache-Control: nocache, private
21 < Connection: keep-alive
21 < Content-Type: application/json
21 < Date: Fri, 25 Aug 2023 08:09:24 GMT
21 < Server: nginx/1.18.0
21 < Transfer-Encoding: chunked
{"data":"foobar2"}

```

You notice that the **expected result** just gets one 401 on the first request with the Digest Header, and then resuses this information on the subsequent requests.
In the **actual result** each request first gets a 401 and then a 200.

The new behavior causes a twice as much requests than than previously.

## Findings

In 2.40 it looks like the cacheKey always uses the URI **including the path**: https://github.com/eclipse-ee4j/jersey/blob/2.40/core-client/src/main/java/org/glassfish/jersey/client/authentication/AuthenticationUtil.java#L49

In 1.19 it seems there was a different mechanism using a ThreadLocal: https://github.com/javaee/jersey-1.x/blob/864a01d7be490ab93d2424da3e446ad8eb84b1e8/jersey-client/src/main/java/com/sun/jersey/api/client/filter/HTTPDigestAuthFilter.java#L386

The RFC 7616 for digest auth says the following about the **domain** attribute:

> from: https://datatracker.ietf.org/doc/html/rfc7616#section-3.3
> domain
>
> A quoted, space-separated list of URIs, as specified in [[RFC3986](https://datatracker.ietf.org/doc/html/rfc3986)],
> that define the protection space. If a URI is a path-absolute, it
> is relative to the canonical root URL. (See Section 2.2 of
> [RFC7235].) An absolute-URI in this list may refer to a different
> server than the web-origin [[RFC6454](https://datatracker.ietf.org/doc/html/rfc6454)]. The client can use this
> list to determine the set of URIs for which the same
> authentication information may be sent: any URI that has a URI in
> this list as a prefix (after both have been made absolute) MAY be
> assumed to be in the same protection space. If this parameter is
> omitted or its value is empty, the client SHOULD assume that the
> protection space consists of all URIs on the web-origin.
>
> This parameter is not meaningful in Proxy-Authenticate header
> fields, for which the protection space is always the entire proxy;
> if present, it MUST be ignored.

The part

> any URI that has a URI in
> this list as a prefix (after both have been made absolute) MAY be
> assumed to be in the same protection space

makes me think, that the domain attribute should be considered for the cacheKey.

In my example above the `domain="/"` would basicaly mean that all request URIs are in the same space and the auth information should be reused for those urls matching this.

GET https://example.com:8080/api/articles/1
GET https://example.com:8080/api/articles/2

everything after the first slash (`/`)

## Suggestion

Maybe it would be most helpful to let the developer customize how the CacheKey is calculated.
Currently `org.glassfish.jersey.client.authentication.AuthenticationUtil.getCacheKey(ClientRequestContext)` is called in 3 places:

image

Example Pseudo code:

```
BiFunction cacheKeyCustomizer = new BiFunction(){

@Override
public URI apply(ClientRequestContext request,
DigestScheme u) {
// TODO do custom cacheKey calculation which considers 'domain' from DigestScheme and the current request
return null;
}

};
HttpAuthenticationFeature authFeature = HttpAuthenticationFeature.digest(this.apiUsername, this.apiKey, cacheKeyCustomizer);
config.register(authFeature);
```

Any thoughts?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.