Improvements to the Jetty client documentation, authentication section.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
512f31b3af
commit
9f38e433d4
|
@ -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.
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue