Improvements to the Jetty client documentation, authentication section.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-04-02 12:19:23 +02:00
parent 512f31b3af
commit 9f38e433d4
2 changed files with 111 additions and 45 deletions

View File

@ -17,75 +17,85 @@
//
[[client-http-authentication]]
=== Authentication Support
=== HttpClient Authentication Support
Jetty's HTTP client supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235].
Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication
mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235],
as well as the SPNEGO authentication mechanism defined in
link:https://tools.ietf.org/html/rfc4559[RFC 4559].
You can configure authentication credentials in the HTTP client instance as follows:
The HTTP _conversation_ - the sequence of related HTTP requests - for a
request that needs authentication is the following:
[source, java, subs="{sub-order}"]
[plantuml]
----
URI uri = new URI("http://domain.com/secure");
String realm = "MyRealm";
String user = "username";
String pass = "password";
skinparam backgroundColor transparent
skinparam monochrome true
skinparam shadowing false
// Add authentication credentials
AuthenticationStore auth = httpClient.getAuthenticationStore();
auth.addAuthentication(new BasicAuthentication(uri, realm, user, pass));
participant Application
participant HttpClient
participant Server
ContentResponse response = httpClient
.newRequest(uri)
.send()
.get(5, TimeUnit.SECONDS);
Application -> Server: GET /path
Server -> HttpClient: 401 + WWW-Authenticate
HttpClient -> Server: GET + Authentication
Server -> Application: 200 OK
----
Jetty's HTTP client tests authentication credentials against the challenge(s) the server issues (see our section here on link:#configuring-security-secure-passwords[secure password obfuscation]), and if they match it automatically sends the right authentication headers to the server for authentication.
If the authentication is successful, it caches the result and reuses it for subsequent requests for the same domain and matching URIs.
Upon receiving a HTTP 401 response code, `HttpClient` looks at the
`WWW-Authenticate` response header (the server _challenge_) and then tries to
match configured authentication credentials to produce an `Authentication`
header that contains the authentication credentials to access the resource.
The HTTP conversation for a successful match is the following:
You can configure authentication credentials in the `HttpClient` instance as
follows:
[source,java,indent=0]
----
Application HttpClient Server
| | |
|--- GET ---|------------ GET ----------->|
| | |
| |<-- 401 + WWW-Authenticate --|
| | |
| |--- GET + Authentication --->|
| | |
|<-- 200 ---|------------ 200 ------------|
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=addAuthentication]
----
The application does not receive events related to the response with code 401, they are handled internally by `HttpClient` which produces a request similar to the original but with the correct `Authorization` header, and then relays the response with code 200 to the application.
``Authentication``s are matched against the server challenge first by
mechanism (e.g. `BASIC` or `DIGEST`), then by realm and then by URI.
Successful authentications are cached, but it is possible to clear them in order to force authentication again:
If an `Authentication` match is found, the application does not receive events
related to the HTTP 401 response. These events are handled internally by
`HttpClient` which produces another (internal) request similar to the original
request but with an additional `Authorization` header.
[source, java, subs="{sub-order}"]
If the authentication is successful, the server responds with a HTTP 200 and
`HttpClient` caches the `Authentication.Result` so that subsequent requests
for a matching URI will not incur in the additional rountrip caused by the
HTTP 401 response.
It is possible to clear ``Authentication.Result``s in order to force
authentication again:
[source,java,indent=0]
----
httpClient.getAuthenticationStore().clearAuthenticationResults();
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=clearResults]
----
Authentications may be preempted to avoid the additional roundtrip due to the server challenge in this way:
Authentication results may be preempted to avoid the additional roundtrip
due to the server challenge in this way:
[source, java, subs="{sub-order}"]
[source,java,indent=0]
----
AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri = URI.create("http://domain.com/secure");
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=preemptedResult]
----
In this way, requests for the given URI are enriched by `HttpClient` immediately with the `Authorization` header, and the server should respond with a 200 and the resource content rather than with the 401 and the challenge.
In this way, requests for the given URI are enriched immediately with the
`Authorization` header, and the server should respond with HTTP 200 (and the
resource content) rather than with the 401 and the challenge.
It is also possible to preempt the authentication for a single request only, in this way:
It is also possible to preempt the authentication for a single request only,
in this way:
[source, java, subs="{sub-order}"]
[source,java,indent=0]
----
URI uri = URI.create("http://domain.com/secure");
Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password")
Request request = httpClient.newRequest(uri);
authn.apply(request);
request.send();
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestPreemptedResult]
----
See also the link:#client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies.
See also the link:#client-http-proxy-authentication[proxy authentication section]
for further information about how authentication works with HTTP proxies.

View File

@ -32,12 +32,15 @@ import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.FutureResponseListener;
@ -563,4 +566,57 @@ public class HTTPClientDocs
httpClient.setCookieStore(new GoogleOnlyCookieStore());
// end::filteringCookieStore[]
}
public void addAuthentication() throws Exception
{
HttpClient httpClient = new HttpClient();
httpClient.start();
// tag::addAuthentication[]
// Add authentication credentials.
AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri1 = new URI("http://mydomain.com/secure");
auth.addAuthentication(new BasicAuthentication(uri1, "MyRealm", "userName1", "password1"));
URI uri2 = new URI("http://otherdomain.com/admin");
auth.addAuthentication(new BasicAuthentication(uri1, "AdminRealm", "admin", "password"));
// end::addAuthentication[]
}
public void clearResults() throws Exception
{
HttpClient httpClient = new HttpClient();
httpClient.start();
// tag::clearResults[]
httpClient.getAuthenticationStore().clearAuthenticationResults();
// end::clearResults[]
}
public void preemptedResult() throws Exception
{
HttpClient httpClient = new HttpClient();
httpClient.start();
// tag::preemptedResult[]
AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri = URI.create("http://domain.com/secure");
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));
// end::preemptedResult[]
}
public void requestPreemptedResult() throws Exception
{
HttpClient httpClient = new HttpClient();
httpClient.start();
// tag::requestPreemptedResult[]
URI uri = URI.create("http://domain.com/secure");
Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password");
Request request = httpClient.newRequest(uri);
authn.apply(request);
request.send();
// end::requestPreemptedResult[]
}
}