You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2023/01/10 19:05:02 UTC

[httpcomponents-client] branch 5.3.x updated (d2016eaac -> d50d9f94e)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a change to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


    from d2016eaac Pass HttpContext to SSLConnectionSocketFactory#prepareSocket method (#404)
     new e24727648 Credentials interface should be able to represent different types of user credentials including token based with no password
     new 8c53de2f8 Removed references in specific RFCs
     new 3a8beba7b Normalize scheme name in AuthScope
     new d8212aff0 Made authenticating decorators capable of supporting different authentication schemes
     new 6ce345d50 New Authenticator interface method to return an auth result with additional challenge parameters
     new d50d9f94e BEARER auth scheme support (RFC 6750)

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../async/AuthenticatingAsyncDecorator.java        |  41 +++++--
 .../auth/AbstractAuthenticationHandler.java        |  77 ++++++++++++
 .../apache/hc/client5/testing/auth/AuthResult.java |  68 +++++------
 ...thenticator.java => AuthenticationHandler.java} |  14 ++-
 .../hc/client5/testing/auth/Authenticator.java     |   7 ++
 .../testing/auth/BasicAuthTokenExtractor.java      |   6 +-
 .../testing/auth/BasicAuthenticationHandler.java   |  48 ++++----
 ...cator.java => BearerAuthenticationHandler.java} |  14 ++-
 .../testing/classic/AuthenticatingDecorator.java   |  42 +++++--
 .../hc/client5/testing/BasicTestAuthenticator.java |  20 ++++
 .../AbstractHttpAsyncClientAuthenticationTest.java |  65 ++++++++++
 .../testing/sync/TestClientAuthentication.java     |  70 ++++++++++-
 .../hc/client5/http/impl/win/WinHttpClients.java   |   2 +
 .../org/apache/hc/client5/http/auth/AuthScope.java |  16 +--
 .../{BasicUserPrincipal.java => BearerToken.java}  |  54 ++++-----
 .../apache/hc/client5/http/auth/Credentials.java   |   5 +
 .../hc/client5/http/auth/StandardAuthScheme.java   |  11 +-
 .../http/auth/UsernamePasswordCredentials.java     |  35 +++++-
 .../http/impl/DefaultAuthenticationStrategy.java   |   1 +
 .../http/impl/async/H2AsyncClientBuilder.java      |   2 +
 .../http/impl/async/HttpAsyncClientBuilder.java    |   2 +
 .../hc/client5/http/impl/auth/BasicScheme.java     |  35 +++---
 .../auth/{BasicScheme.java => BearerScheme.java}   | 132 +++++----------------
 ...SchemeFactory.java => BearerSchemeFactory.java} |  18 +--
 .../hc/client5/http/impl/auth/DigestScheme.java    |  28 ++---
 .../http/impl/classic/HttpClientBuilder.java       |   2 +
 .../apache/hc/client5/http/auth/TestAuthScope.java |   4 +-
 .../hc/client5/http/auth/TestCredentials.java      |  32 ++++-
 .../client5/http/impl/auth/TestBearerScheme.java   | 104 ++++++++++++++++
 .../auth/TestSystemDefaultCredentialsProvider.java |  18 +--
 30 files changed, 682 insertions(+), 291 deletions(-)
 create mode 100644 httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
 copy httpclient5/src/main/java/org/apache/hc/client5/http/auth/BasicUserPrincipal.java => httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthResult.java (55%)
 copy httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/{Authenticator.java => AuthenticationHandler.java} (80%)
 copy httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestSchemeFactory.java => httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java (57%)
 copy httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/{Authenticator.java => BearerAuthenticationHandler.java} (78%)
 copy httpclient5/src/main/java/org/apache/hc/client5/http/auth/{BasicUserPrincipal.java => BearerToken.java} (64%)
 copy httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/{BasicScheme.java => BearerScheme.java} (54%)
 copy httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/{NTLMSchemeFactory.java => BearerSchemeFactory.java} (83%)
 create mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java


[httpcomponents-client] 05/06: New Authenticator interface method to return an auth result with additional challenge parameters

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 6ce345d5089813ac57fb15dd26d81fbe546d962d
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Tue Dec 6 21:05:23 2022 +0100

    New Authenticator interface method to return an auth result with additional challenge parameters
---
 .../async/AuthenticatingAsyncDecorator.java        | 21 +++--
 .../auth/AbstractAuthenticationHandler.java        |  2 +-
 .../apache/hc/client5/testing/auth/AuthResult.java | 92 ++++++++++++++++++++++
 .../hc/client5/testing/auth/Authenticator.java     |  7 ++
 .../testing/auth/BasicAuthenticationHandler.java   | 21 ++++-
 .../testing/classic/AuthenticatingDecorator.java   | 22 ++++--
 6 files changed, 151 insertions(+), 14 deletions(-)

diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
index f0bff871d..1f621aa92 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
@@ -28,10 +28,12 @@ package org.apache.hc.client5.testing.async;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.hc.client5.testing.auth.AuthResult;
 import org.apache.hc.client5.testing.auth.AuthenticationHandler;
 import org.apache.hc.client5.testing.auth.Authenticator;
 import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler;
@@ -43,6 +45,7 @@ import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
@@ -77,7 +80,7 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler
     }
 
     public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler, final Authenticator authenticator) {
-        this(exchangeHandler, new BasicAuthenticationHandler(), authenticator);
+        this(exchangeHandler, new BasicAuthenticationHandler(StandardCharsets.US_ASCII), authenticator);
     }
 
     protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
@@ -95,20 +98,26 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler
         final URIAuthority authority = request.getAuthority();
         final String requestUri = request.getRequestUri();
 
-        final boolean authenticated = authenticator.authenticate(authority, requestUri, challengeResponse);
+        final AuthResult authResult = authenticator.perform(authority, requestUri, challengeResponse);
         final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
         final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
 
-        if (authenticated) {
+        if (authResult.isSuccess()) {
             if (expectContinue) {
                 responseChannel.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE), context);
             }
             exchangeHandler.handleRequest(request, entityDetails, responseChannel, context);
         } else {
             final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
+            final List<NameValuePair> challengeParams = new ArrayList<>();
             final String realm = authenticator.getRealm(authority, requestUri);
-            final String challenge = authenticationHandler.challenge(
-                    realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null);
+            if (realm != null) {
+                challengeParams.add(new BasicNameValuePair("realm", realm));
+            }
+            if (authResult.hasParams()) {
+                challengeParams.addAll(authResult.getParams());
+            }
+            final String challenge = authenticationHandler.challenge(challengeParams);
             unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge);
             customizeUnauthorizedResponse(unauthorized);
 
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
index 7ba77f3b4..5c2420bb2 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
@@ -54,7 +54,7 @@ abstract class AbstractAuthenticationHandler implements AuthenticationHandler<St
         return buf.toString();
     }
 
-    abstract String decodeChallenge(String challenge) throws IllegalArgumentException;
+    abstract String decodeChallenge(String challenge);
 
     public final String extractAuthToken(final String challengeResponse) throws HttpException {
         final int i = challengeResponse.indexOf(' ');
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthResult.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthResult.java
new file mode 100644
index 000000000..5ee1deb36
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthResult.java
@@ -0,0 +1,92 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.testing.auth;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.util.LangUtils;
+
+public final class AuthResult {
+
+    private final boolean success;
+    private final List<NameValuePair> params;
+
+    public AuthResult(final boolean success, final List<NameValuePair> params) {
+        this.success = success;
+        this.params = params != null ? Collections.unmodifiableList(params) : Collections.emptyList();
+    }
+
+    public AuthResult(final boolean success, final NameValuePair... params) {
+        this(success, Arrays.asList(params));
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public boolean hasParams() {
+        return !params.isEmpty();
+    }
+
+    public List<NameValuePair> getParams() {
+        return params;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = LangUtils.HASH_SEED;
+        hash = LangUtils.hashCode(hash, this.success);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof AuthResult) {
+            final AuthResult that = (AuthResult) o;
+            return this.success == that.success;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append(success);
+        if (!params.isEmpty()) {
+            buf.append(" ").append(params);
+        }
+        return buf.toString();
+    }
+
+}
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java
index 911b3c455..0b6dda974 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java
@@ -31,6 +31,13 @@ import org.apache.hc.core5.net.URIAuthority;
 
 public interface Authenticator {
 
+    /**
+     * @since 5.3
+     */
+    default AuthResult perform(URIAuthority authority, String requestUri, String credentials) {
+        return new AuthResult(authenticate(authority, requestUri, credentials));
+    }
+
     boolean authenticate(URIAuthority authority, String requestUri, String credentials);
 
     String getRealm(URIAuthority authority, String requestUri);
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
index c77e83349..3bbe32b07 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
@@ -27,13 +27,32 @@
 
 package org.apache.hc.client5.testing.auth;
 
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.client5.http.utils.Base64;
+import org.apache.hc.core5.util.Args;
 
 public class BasicAuthenticationHandler extends AbstractAuthenticationHandler {
 
+    private final Charset charset;
+
+    /**
+     * @since 5.3
+     */
+    public BasicAuthenticationHandler(final Charset charset) {
+        this.charset = Args.notNull(charset, "Charset");
+    }
+
+    /**
+     * @deprecated Use {@link #BasicAuthenticationHandler(Charset)}
+     */
+    @Deprecated
+    public BasicAuthenticationHandler() {
+        this(StandardCharsets.US_ASCII);
+    }
+
     @Override
     String getSchemeName() {
         return StandardAuthScheme.BASIC;
@@ -43,7 +62,7 @@ public class BasicAuthenticationHandler extends AbstractAuthenticationHandler {
     String decodeChallenge(final String challenge) throws IllegalArgumentException {
         final byte[] bytes = challenge.getBytes(StandardCharsets.US_ASCII);
         final Base64 codec = new Base64();
-        return new String(codec.decode(bytes), StandardCharsets.US_ASCII);
+        return new String(codec.decode(bytes), charset);
     }
 
 }
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
index e0598043e..cc116e4e3 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
@@ -28,8 +28,11 @@
 package org.apache.hc.client5.testing.classic;
 
 import java.io.IOException;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.hc.client5.testing.auth.AuthResult;
 import org.apache.hc.client5.testing.auth.AuthenticationHandler;
 import org.apache.hc.client5.testing.auth.Authenticator;
 import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler;
@@ -39,6 +42,7 @@ import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.io.HttpServerRequestHandler;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
@@ -67,7 +71,7 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler {
 
     public AuthenticatingDecorator(final HttpServerRequestHandler requestHandler,
                                    final Authenticator authenticator) {
-        this(requestHandler, new BasicAuthenticationHandler(), authenticator);
+        this(requestHandler, new BasicAuthenticationHandler(StandardCharsets.US_ASCII), authenticator);
     }
 
     protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) {
@@ -84,20 +88,26 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler {
         final URIAuthority authority = request.getAuthority();
         final String requestUri = request.getRequestUri();
 
-        final boolean authenticated = authenticator.authenticate(authority, requestUri, challengeResponse);
+        final AuthResult authResult = authenticator.perform(authority, requestUri, challengeResponse);
         final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
         final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
 
-        if (authenticated) {
+        if (authResult.isSuccess()) {
             if (expectContinue) {
                 responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE));
             }
             requestHandler.handle(request, responseTrigger, context);
         } else {
             final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
+            final List<NameValuePair> challengeParams = new ArrayList<>();
             final String realm = authenticator.getRealm(authority, requestUri);
-            final String challenge = authenticationHandler.challenge(
-                    realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null);
+            if (realm != null) {
+                challengeParams.add(new BasicNameValuePair("realm", realm));
+            }
+            if (authResult.hasParams()) {
+                challengeParams.addAll(authResult.getParams());
+            }
+            final String challenge = authenticationHandler.challenge(challengeParams);
             unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge);
             customizeUnauthorizedResponse(unauthorized);
             if (unauthorized.getEntity() == null) {


[httpcomponents-client] 01/06: Credentials interface should be able to represent different types of user credentials including token based with no password

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit e247276489e0e2b5732661d8bce6c30e299559b4
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Nov 26 18:28:36 2022 +0100

    Credentials interface should be able to represent different types of user credentials including token based with no password
---
 .../apache/hc/client5/http/auth/Credentials.java   |  5 ++++
 .../http/auth/UsernamePasswordCredentials.java     | 35 ++++++++++++++++++----
 .../hc/client5/http/impl/auth/BasicScheme.java     | 33 ++++++++------------
 .../hc/client5/http/impl/auth/DigestScheme.java    | 23 +++++++-------
 .../hc/client5/http/auth/TestCredentials.java      |  4 +--
 .../auth/TestSystemDefaultCredentialsProvider.java |  2 --
 6 files changed, 61 insertions(+), 41 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/Credentials.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/Credentials.java
index 742ba9e7b..491ee0072 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/Credentials.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/Credentials.java
@@ -39,6 +39,11 @@ public interface Credentials {
 
     Principal getUserPrincipal();
 
+    /**
+     * @deprecated Use specific credentials class that represent a username / password
+     * set of a security token.
+     */
+    @Deprecated
     char[] getPassword();
 
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/UsernamePasswordCredentials.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/UsernamePasswordCredentials.java
index 54fd469bc..f35f3fc5c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/UsernamePasswordCredentials.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/UsernamePasswordCredentials.java
@@ -45,22 +45,36 @@ public class UsernamePasswordCredentials implements Credentials, Serializable {
 
     private static final long serialVersionUID = 243343858802739403L;
 
-    private final BasicUserPrincipal principal;
+    private final Principal principal;
     private final char[] password;
 
     /**
      * The constructor with the username and password arguments.
      *
-     * @param userName the user name
+     * @param principal the user principal
      * @param password the password
+     *
+     * @since 5.3
+     *
+     * @see BasicUserPrincipal
+     * @see NTUserPrincipal
      */
-    public UsernamePasswordCredentials(final String userName, final char[] password) {
+    public UsernamePasswordCredentials(final Principal principal, final char[] password) {
         super();
-        Args.notNull(userName, "Username");
-        this.principal = new BasicUserPrincipal(userName);
+        this.principal = Args.notNull(principal, "User principal");
         this.password = password;
     }
 
+    /**
+     * The constructor with the username and password arguments.
+     *
+     * @param username the user name
+     * @param password the password
+     */
+    public UsernamePasswordCredentials(final String username, final char[] password) {
+        this(new BasicUserPrincipal(username), password);
+    }
+
     @Override
     public Principal getUserPrincipal() {
         return this.principal;
@@ -70,6 +84,17 @@ public class UsernamePasswordCredentials implements Credentials, Serializable {
         return this.principal.getName();
     }
 
+    /**
+     * @since 5.3
+     */
+    public char[] getUserPassword() {
+        return password;
+    }
+
+    /**
+     * @deprecated Use {@link #getUserPassword()}.
+     */
+    @Deprecated
     @Override
     public char[] getPassword() {
         return password;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
index 87d11448c..d6d0d027c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
@@ -39,7 +39,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
-import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.AuthScope;
@@ -49,7 +48,9 @@ import org.apache.hc.client5.http.auth.Credentials;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.MalformedChallengeException;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.utils.ByteArrayBuilder;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
@@ -77,8 +78,7 @@ public class BasicScheme implements AuthScheme, Serializable {
     private transient Base64 base64codec;
     private boolean complete;
 
-    private String username;
-    private char[] password;
+    private UsernamePasswordCredentials credentials;
 
     /**
      * @since 4.3
@@ -93,21 +93,13 @@ public class BasicScheme implements AuthScheme, Serializable {
         this(StandardCharsets.US_ASCII);
     }
 
-    private void applyCredentials(final Credentials credentials) {
-        this.username = credentials.getUserPrincipal().getName();
-        this.password = credentials.getPassword();
-    }
-
-    private void clearCredentials() {
-        this.username = null;
-        this.password = null;
-    }
-
     public void initPreemptive(final Credentials credentials) {
         if (credentials != null) {
-            applyCredentials(credentials);
+            Args.check(credentials instanceof UsernamePasswordCredentials,
+                    "Unsupported credential type: " + credentials.getClass());
+            this.credentials = (UsernamePasswordCredentials) credentials;
         } else {
-            clearCredentials();
+            this.credentials = null;
         }
     }
 
@@ -157,8 +149,8 @@ public class BasicScheme implements AuthScheme, Serializable {
         final AuthScope authScope = new AuthScope(host, getRealm(), getName());
         final Credentials credentials = credentialsProvider.getCredentials(
                 authScope, context);
-        if (credentials != null) {
-            applyCredentials(credentials);
+        if (credentials instanceof UsernamePasswordCredentials) {
+            this.credentials = (UsernamePasswordCredentials) credentials;
             return true;
         }
 
@@ -167,7 +159,7 @@ public class BasicScheme implements AuthScheme, Serializable {
             final String exchangeId = clientContext.getExchangeId();
             LOG.debug("{} No credentials found for auth scope [{}]", exchangeId, authScope);
         }
-        clearCredentials();
+        this.credentials = null;
         return false;
     }
 
@@ -177,9 +169,10 @@ public class BasicScheme implements AuthScheme, Serializable {
     }
 
     private void validateUsername() throws AuthenticationException {
-        if (username == null) {
+        if (credentials == null) {
             throw new AuthenticationException("User credentials not set");
         }
+        final String username = credentials.getUserName();
         for (int i = 0; i < username.length(); i++) {
             final char ch = username.charAt(i);
             if (Character.isISOControl(ch)) {
@@ -204,7 +197,7 @@ public class BasicScheme implements AuthScheme, Serializable {
         }
         final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), defaultCharset);
         this.buffer.charset(charset);
-        this.buffer.append(this.username).append(":").append(this.password);
+        this.buffer.append(this.credentials.getUserName()).append(":").append(this.credentials.getUserPassword());
         if (this.base64codec == null) {
             this.base64codec = new Base64();
         }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
index ff98ab36a..f73f59055 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
@@ -53,6 +53,7 @@ import org.apache.hc.client5.http.auth.Credentials;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.MalformedChallengeException;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.utils.ByteArrayBuilder;
 import org.apache.hc.core5.annotation.Internal;
@@ -118,8 +119,7 @@ public class DigestScheme implements AuthScheme, Serializable {
     private byte[] a1;
     private byte[] a2;
 
-    private String username;
-    private char[] password;
+    private UsernamePasswordCredentials credentials;
 
     public DigestScheme() {
         this(StandardCharsets.ISO_8859_1);
@@ -133,8 +133,9 @@ public class DigestScheme implements AuthScheme, Serializable {
 
     public void initPreemptive(final Credentials credentials, final String cnonce, final String realm) {
         Args.notNull(credentials, "Credentials");
-        this.username = credentials.getUserPrincipal().getName();
-        this.password = credentials.getPassword();
+        Args.check(credentials instanceof UsernamePasswordCredentials,
+                "Unsupported credential type: " + credentials.getClass());
+        this.credentials = (UsernamePasswordCredentials) credentials;
         this.paramMap.put("cnonce", cnonce);
         this.paramMap.put("realm", realm);
     }
@@ -190,9 +191,8 @@ public class DigestScheme implements AuthScheme, Serializable {
         final AuthScope authScope = new AuthScope(host, getRealm(), getName());
         final Credentials credentials = credentialsProvider.getCredentials(
                 authScope, context);
-        if (credentials != null) {
-            this.username = credentials.getUserPrincipal().getName();
-            this.password = credentials.getPassword();
+        if (credentials instanceof UsernamePasswordCredentials) {
+            this.credentials = (UsernamePasswordCredentials) credentials;
             return true;
         }
 
@@ -201,8 +201,7 @@ public class DigestScheme implements AuthScheme, Serializable {
             final String exchangeId = clientContext.getExchangeId();
             LOG.debug("{} No credentials found for auth scope [{}]", exchangeId, authScope);
         }
-        this.username = null;
-        this.password = null;
+        this.credentials = null;
         return false;
     }
 
@@ -323,13 +322,13 @@ public class DigestScheme implements AuthScheme, Serializable {
             //      ":" unq(cnonce-value)
 
             // calculated one per session
-            buffer.append(username).append(":").append(realm).append(":").append(password);
+            buffer.append(credentials.getUserName()).append(":").append(realm).append(":").append(credentials.getUserPassword());
             final String checksum = formatHex(digester.digest(this.buffer.toByteArray()));
             buffer.reset();
             buffer.append(checksum).append(":").append(nonce).append(":").append(cnonce);
         } else {
             // unq(username-value) ":" unq(realm-value) ":" passwd
-            buffer.append(username).append(":").append(realm).append(":").append(password);
+            buffer.append(credentials.getUserName()).append(":").append(realm).append(":").append(credentials.getUserPassword());
         }
         a1 = buffer.toByteArray();
 
@@ -390,7 +389,7 @@ public class DigestScheme implements AuthScheme, Serializable {
         buffer.append(StandardAuthScheme.DIGEST + " ");
 
         final List<BasicNameValuePair> params = new ArrayList<>(20);
-        params.add(new BasicNameValuePair("username", username));
+        params.add(new BasicNameValuePair("username", credentials.getUserName()));
         params.add(new BasicNameValuePair("realm", realm));
         params.add(new BasicNameValuePair("nonce", nonce));
         params.add(new BasicNameValuePair("uri", uri));
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
index a3b1e7926..de14d7c48 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
@@ -44,14 +44,14 @@ public class TestCredentials {
         Assertions.assertEquals("name", creds1.getUserName());
         Assertions.assertEquals(new BasicUserPrincipal("name"),
                 creds1.getUserPrincipal());
-        Assertions.assertArrayEquals("pwd".toCharArray(), creds1.getPassword());
+        Assertions.assertArrayEquals("pwd".toCharArray(), creds1.getUserPassword());
         Assertions.assertEquals("[principal: name]", creds1.toString());
         final UsernamePasswordCredentials creds2 = new UsernamePasswordCredentials(
             "name", null);
         Assertions.assertEquals("name", creds2.getUserName());
         Assertions.assertEquals(new BasicUserPrincipal("name"),
                 creds2.getUserPrincipal());
-        Assertions.assertNull(creds2.getPassword());
+        Assertions.assertNull(creds2.getUserPassword());
         Assertions.assertEquals("[principal: name]", creds2.toString());
     }
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
index 2c452a3f9..265b4c980 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
@@ -104,7 +104,6 @@ public class TestSystemDefaultCredentialsProvider {
                                                                         RequestorType.SERVER);
         Assertions.assertNotNull(receivedCredentials);
         Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName());
-        Assertions.assertEquals(AUTH1.getPassword(), receivedCredentials.getPassword());
     }
 
     @Test
@@ -122,7 +121,6 @@ public class TestSystemDefaultCredentialsProvider {
                                                                         RequestorType.SERVER);
         Assertions.assertNotNull(receivedCredentials);
         Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName());
-        Assertions.assertEquals(AUTH1.getPassword(), receivedCredentials.getPassword());
     }
 
     private AuthenticatorDelegate installAuthenticator(final PasswordAuthentication returedAuthentication) {


[httpcomponents-client] 06/06: BEARER auth scheme support (RFC 6750)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit d50d9f94e1faf2f4dd431a5f84f23c8db8e1be57
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Tue Dec 6 21:05:56 2022 +0100

    BEARER auth scheme support (RFC 6750)
---
 .../testing/auth/BearerAuthenticationHandler.java} |  25 +--
 .../hc/client5/testing/BasicTestAuthenticator.java |  20 +++
 .../AbstractHttpAsyncClientAuthenticationTest.java |  65 ++++++++
 .../testing/sync/TestClientAuthentication.java     |  70 ++++++++-
 .../hc/client5/http/impl/win/WinHttpClients.java   |   2 +
 .../apache/hc/client5/http/auth/BearerToken.java   |  90 +++++++++++
 .../hc/client5/http/auth/StandardAuthScheme.java   |   7 +-
 .../http/impl/DefaultAuthenticationStrategy.java   |   1 +
 .../http/impl/async/H2AsyncClientBuilder.java      |   2 +
 .../http/impl/async/HttpAsyncClientBuilder.java    |   2 +
 .../hc/client5/http/impl/auth/BearerScheme.java    | 169 +++++++++++++++++++++
 .../http/impl/auth/BearerSchemeFactory.java        |  37 ++---
 .../http/impl/classic/HttpClientBuilder.java       |   2 +
 .../hc/client5/http/auth/TestCredentials.java      |  28 ++++
 .../client5/http/impl/auth/TestBearerScheme.java   | 104 +++++++++++++
 15 files changed, 586 insertions(+), 38 deletions(-)

diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BearerAuthenticationHandler.java
similarity index 62%
copy from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
copy to httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BearerAuthenticationHandler.java
index aeac33fa3..0f2362fd7 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BearerAuthenticationHandler.java
@@ -25,31 +25,20 @@
  *
  */
 
-package org.apache.hc.client5.testing;
+package org.apache.hc.client5.testing.auth;
 
-import java.util.Objects;
+import org.apache.hc.client5.http.auth.StandardAuthScheme;
 
-import org.apache.hc.client5.testing.auth.Authenticator;
-import org.apache.hc.core5.net.URIAuthority;
-
-public class BasicTestAuthenticator implements Authenticator {
-
-    private final String userToken;
-    private final String realm;
-
-    public BasicTestAuthenticator(final String userToken, final String realm) {
-        this.userToken = userToken;
-        this.realm = realm;
-    }
+public class BearerAuthenticationHandler extends AbstractAuthenticationHandler {
 
     @Override
-    public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
-        return Objects.equals(userToken, credentials);
+    String getSchemeName() {
+        return StandardAuthScheme.BEARER;
     }
 
     @Override
-    public String getRealm(final URIAuthority authority, final String requestUri) {
-        return realm;
+    String decodeChallenge(final String challenge) {
+        return challenge;
     }
 
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
index aeac33fa3..eb7f3831a 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
@@ -29,8 +29,11 @@ package org.apache.hc.client5.testing;
 
 import java.util.Objects;
 
+import org.apache.hc.client5.testing.auth.AuthResult;
 import org.apache.hc.client5.testing.auth.Authenticator;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.util.TextUtils;
 
 public class BasicTestAuthenticator implements Authenticator {
 
@@ -47,6 +50,23 @@ public class BasicTestAuthenticator implements Authenticator {
         return Objects.equals(userToken, credentials);
     }
 
+    @Override
+    public AuthResult perform(final URIAuthority authority,
+                              final String requestUri,
+                              final String credentials) {
+        final boolean result = authenticate(authority, requestUri, credentials);
+        if (result) {
+            return new AuthResult(true);
+        } else {
+            if (TextUtils.isBlank(credentials)) {
+                return new AuthResult(false);
+            } else {
+                final String error = credentials.endsWith("-expired") ? "token expired"  : "invalid token";
+                return new AuthResult(false, new BasicNameValuePair("error", error));
+            }
+        }
+    }
+
     @Override
     public String getRealm(final URIAuthority authority, final String requestUri) {
         return realm;
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthenticationTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthenticationTest.java
index 3dba15041..f9867fde5 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthenticationTest.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthenticationTest.java
@@ -28,6 +28,7 @@ package org.apache.hc.client5.testing.async;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Queue;
@@ -45,6 +46,7 @@ import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
 import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
 import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.BearerToken;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -57,6 +59,7 @@ import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.testing.BasicTestAuthenticator;
 import org.apache.hc.client5.testing.auth.Authenticator;
+import org.apache.hc.client5.testing.auth.BearerAuthenticationHandler;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpHeaders;
@@ -475,4 +478,66 @@ public abstract class AbstractHttpAsyncClientAuthenticationTest<T extends Closea
                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
     }
 
+    private final static String CHARS = "0123456789abcdef";
+
+    @Test
+    public void testBearerTokenAuthentication() throws Exception {
+        final SecureRandom secureRandom = SecureRandom.getInstanceStrong();
+        secureRandom.setSeed(System.currentTimeMillis());
+        final StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < 16; i++) {
+            buf.append(CHARS.charAt(secureRandom.nextInt(CHARS.length() - 1)));
+        }
+        final String token = buf.toString();
+        final H2TestServer server = startServer(requestHandler ->
+                new AuthenticatingAsyncDecorator(
+                        requestHandler,
+                        new BearerAuthenticationHandler(),
+                        new BasicTestAuthenticator(token, "test realm")));
+        server.register("*", AsyncEchoHandler::new);
+        final HttpHost target = targetHost();
+
+        final T client = startClient();
+
+        final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
+        final HttpClientContext context1 = HttpClientContext.create();
+        context1.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future1 = client.execute(SimpleRequestBuilder.get()
+                .setHttpHost(target)
+                .setPath("/")
+                .build(), context1, null);
+        final SimpleHttpResponse response1 = future1.get();
+        Assertions.assertNotNull(response1);
+        Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode());
+        Mockito.verify(credsProvider).getCredentials(
+                Mockito.eq(new AuthScope(target, "test realm", "bearer")), Mockito.any());
+
+        final HttpClientContext context2 = HttpClientContext.create();
+        Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
+                .thenReturn(new BearerToken(token));
+        context2.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future2 = client.execute(SimpleRequestBuilder.get()
+                .setHttpHost(target)
+                .setPath("/")
+                .build(), context2, null);
+        final SimpleHttpResponse response2 = future2.get();
+        Assertions.assertNotNull(response2);
+        Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
+
+        final HttpClientContext context3 = HttpClientContext.create();
+        Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
+                .thenReturn(new BearerToken(token + "-expired"));
+        context3.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future3 = client.execute(SimpleRequestBuilder.get()
+                .setHttpHost(target)
+                .setPath("/")
+                .build(), context3, null);
+        final SimpleHttpResponse response3 = future3.get();
+        Assertions.assertNotNull(response3);
+        Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response3.getCode());
+    }
+
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
index 441d934ee..27f2a3f03 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
@@ -31,6 +31,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Queue;
@@ -44,6 +45,7 @@ import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
 import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.BearerToken;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -61,6 +63,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.testing.BasicTestAuthenticator;
 import org.apache.hc.client5.testing.auth.Authenticator;
+import org.apache.hc.client5.testing.auth.BearerAuthenticationHandler;
 import org.apache.hc.client5.testing.classic.AuthenticatingDecorator;
 import org.apache.hc.client5.testing.classic.EchoHandler;
 import org.apache.hc.client5.testing.sync.extension.TestClientResources;
@@ -424,7 +427,7 @@ public class TestClientAuthentication {
     }
 
     @Test
-    public void testAuthenticationCredentialsCachingReauthenticationOnDifferentRealm() throws Exception {
+    public void testAuthenticationCredentialsCachingReAuthenticationOnDifferentRealm() throws Exception {
         final ClassicTestServer server = startServer(new Authenticator() {
 
             @Override
@@ -762,4 +765,69 @@ public class TestClientAuthentication {
                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
     }
 
+    private final static String CHARS = "0123456789abcdef";
+
+    @Test
+    public void testBearerTokenAuthentication() throws Exception {
+        final SecureRandom secureRandom = SecureRandom.getInstanceStrong();
+        secureRandom.setSeed(System.currentTimeMillis());
+        final StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < 16; i++) {
+            buf.append(CHARS.charAt(secureRandom.nextInt(CHARS.length() - 1)));
+        }
+        final String token = buf.toString();
+        final ClassicTestServer server = testResources.startServer(
+                Http1Config.DEFAULT,
+                HttpProcessors.server(),
+                requestHandler -> new AuthenticatingDecorator(
+                        requestHandler,
+                        new BearerAuthenticationHandler(),
+                        new BasicTestAuthenticator(token, "test realm")));
+        server.registerHandler("*", new EchoHandler());
+        final HttpHost target = targetHost();
+
+        final CloseableHttpClient client = startClient();
+
+        final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
+
+        final HttpClientContext context1 = HttpClientContext.create();
+        context1.setCredentialsProvider(credsProvider);
+        final HttpGet httpget1 = new HttpGet("/");
+        client.execute(target, httpget1, context1, response -> {
+            final HttpEntity entity = response.getEntity();
+            Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+            Assertions.assertNotNull(entity);
+            EntityUtils.consume(entity);
+            return null;
+        });
+        Mockito.verify(credsProvider).getCredentials(
+                Mockito.eq(new AuthScope(target, "test realm", "bearer")), Mockito.any());
+
+        final HttpClientContext context2 = HttpClientContext.create();
+        Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
+                .thenReturn(new BearerToken(token));
+        context2.setCredentialsProvider(credsProvider);
+        final HttpGet httpget2 = new HttpGet("/");
+        client.execute(target, httpget2, context2, response -> {
+            final HttpEntity entity = response.getEntity();
+            Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assertions.assertNotNull(entity);
+            EntityUtils.consume(entity);
+            return null;
+        });
+
+        final HttpClientContext context3 = HttpClientContext.create();
+        Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
+                .thenReturn(new BearerToken(token + "-expired"));
+        context3.setCredentialsProvider(credsProvider);
+        final HttpGet httpget3 = new HttpGet("/");
+        client.execute(target, httpget3, context3, response -> {
+            final HttpEntity entity = response.getEntity();
+            Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+            Assertions.assertNotNull(entity);
+            EntityUtils.consume(entity);
+            return null;
+        });
+    }
+
 }
diff --git a/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WinHttpClients.java b/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WinHttpClients.java
index ef4fca281..c36511740 100644
--- a/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WinHttpClients.java
+++ b/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WinHttpClients.java
@@ -31,6 +31,7 @@ import java.util.Locale;
 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.BearerSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@@ -60,6 +61,7 @@ public class WinHttpClients {
             final Registry<AuthSchemeFactory> authSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
                     .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
+                    .register(StandardAuthScheme.BEARER, BearerSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.NTLM, WindowsNTLMSchemeFactory.DEFAULT)
                     .register(StandardAuthScheme.SPNEGO, WindowsNegotiateSchemeFactory.DEFAULT)
                     .build();
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/BearerToken.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/BearerToken.java
new file mode 100644
index 000000000..f4a533161
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/BearerToken.java
@@ -0,0 +1,90 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.auth;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Objects;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * Opaque token {@link Credentials} usually representing a set of claims, often encrypted
+ * or signed. The JWT (JSON Web Token) is among most widely used tokens used at the time
+ * of writing.
+ *
+ * @since 5.3
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class BearerToken implements Credentials, Serializable {
+
+    private final String token;
+
+    public BearerToken(final String token) {
+        super();
+        this.token = Args.notBlank(token, "Token");
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        return null;
+    }
+
+    /**
+     * @deprecated Do not use.
+     */
+    @Deprecated
+    @Override
+    public char[] getPassword() {
+        return null;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    @Override
+    public int hashCode() {
+        return token.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof BearerToken) {
+            final BearerToken that = (BearerToken) o;
+            return Objects.equals(this.token, that.token);
+        }
+        return false;
+    }
+
+}
+
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
index 51371cc7b..b5994a91c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
@@ -39,7 +39,7 @@ public final class StandardAuthScheme {
     }
 
     /**
-     * Basic authentication scheme (considered inherently insecure without transport encryption,
+     * Basic authentication scheme (considered inherently insecure without TLS,
      * but most widely supported).
      */
     public static final String BASIC = "Basic";
@@ -49,6 +49,11 @@ public final class StandardAuthScheme {
      */
     public static final String DIGEST = "Digest";
 
+    /**
+     * Bearer authentication scheme (should be used with TLS).
+     */
+    public static final String BEARER = "Bearer";
+
     /**
      * The NTLM authentication scheme is a proprietary Microsoft Windows
      * authentication protocol as defined in [MS-NLMP].
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
index a12b137ff..64559c4ff 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
@@ -68,6 +68,7 @@ public class DefaultAuthenticationStrategy implements AuthenticationStrategy {
                 StandardAuthScheme.SPNEGO,
                 StandardAuthScheme.KERBEROS,
                 StandardAuthScheme.NTLM,
+                StandardAuthScheme.BEARER,
                 StandardAuthScheme.DIGEST,
                 StandardAuthScheme.BASIC));
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
index 3cdc556ee..5c594c7ad 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
@@ -58,6 +58,7 @@ import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.BearerSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
@@ -820,6 +821,7 @@ public class H2AsyncClientBuilder {
             authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeFactory>create()
                     .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
+                    .register(StandardAuthScheme.BEARER, BearerSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.SPNEGO, SPNegoSchemeFactory.DEFAULT)
                     .register(StandardAuthScheme.KERBEROS, KerberosSchemeFactory.DEFAULT)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index 09d0657d2..1012284e1 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -64,6 +64,7 @@ import org.apache.hc.client5.http.impl.IdleConnectionEvictor;
 import org.apache.hc.client5.http.impl.NoopUserTokenHandler;
 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.BearerSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
@@ -990,6 +991,7 @@ public class HttpAsyncClientBuilder {
             authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeFactory>create()
                     .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
+                    .register(StandardAuthScheme.BEARER, BearerSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE)
                     .register(StandardAuthScheme.SPNEGO, SPNegoSchemeFactory.DEFAULT)
                     .register(StandardAuthScheme.KERBEROS, KerberosSchemeFactory.DEFAULT)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java
new file mode 100644
index 000000000..02fcb7a3c
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java
@@ -0,0 +1,169 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.auth;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.hc.client5.http.auth.AuthChallenge;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.AuthStateCacheable;
+import org.apache.hc.client5.http.auth.AuthenticationException;
+import org.apache.hc.client5.http.auth.BearerToken;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.MalformedChallengeException;
+import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Asserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Bearer authentication scheme.
+ *
+ * @since 5.3
+ */
+@AuthStateCacheable
+public class BearerScheme implements AuthScheme, Serializable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BearerScheme.class);
+
+    private final Map<String, String> paramMap;
+    private boolean complete;
+
+    private BearerToken bearerToken;
+
+    public BearerScheme() {
+        this.paramMap = new HashMap<>();
+        this.complete = false;
+    }
+
+    @Override
+    public String getName() {
+        return StandardAuthScheme.BEARER;
+    }
+
+    @Override
+    public boolean isConnectionBased() {
+        return false;
+    }
+
+    @Override
+    public String getRealm() {
+        return this.paramMap.get("realm");
+    }
+
+    @Override
+    public void processChallenge(
+            final AuthChallenge authChallenge,
+            final HttpContext context) throws MalformedChallengeException {
+        this.paramMap.clear();
+        final List<NameValuePair> params = authChallenge.getParams();
+        if (params != null) {
+            for (final NameValuePair param: params) {
+                this.paramMap.put(param.getName().toLowerCase(Locale.ROOT), param.getValue());
+            }
+            if (LOG.isDebugEnabled()) {
+                final String error = paramMap.get("error");
+                if (error != null) {
+                    final StringBuilder buf = new StringBuilder();
+                    buf.append(error);
+                    final String desc = paramMap.get("error_description");
+                    final String uri = paramMap.get("error_uri");
+                    if (desc != null || uri != null) {
+                        buf.append(" (");
+                        buf.append(desc).append("; ").append(uri);
+                        buf.append(")");
+                    }
+                    LOG.debug(buf.toString());
+                }
+            }
+        }
+        this.complete = true;
+    }
+
+    @Override
+    public boolean isChallengeComplete() {
+        return this.complete;
+    }
+
+    @Override
+    public boolean isResponseReady(
+            final HttpHost host,
+            final CredentialsProvider credentialsProvider,
+            final HttpContext context) throws AuthenticationException {
+
+        Args.notNull(host, "Auth host");
+        Args.notNull(credentialsProvider, "Credentials provider");
+
+        final AuthScope authScope = new AuthScope(host, getRealm(), getName());
+        final Credentials credentials = credentialsProvider.getCredentials(authScope, context);
+        if (credentials instanceof BearerToken) {
+            this.bearerToken = (BearerToken) credentials;
+            return true;
+        }
+
+        if (LOG.isDebugEnabled()) {
+            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            final String exchangeId = clientContext.getExchangeId();
+            LOG.debug("{} No credentials found for auth scope [{}]", exchangeId, authScope);
+        }
+        this.bearerToken = null;
+        return false;
+    }
+
+    @Override
+    public Principal getPrincipal() {
+        return null;
+    }
+
+    @Override
+    public String generateAuthResponse(
+            final HttpHost host,
+            final HttpRequest request,
+            final HttpContext context) throws AuthenticationException {
+        Asserts.notNull(bearerToken, "Bearer token");
+        return StandardAuthScheme.BEARER + " " + bearerToken.getToken();
+    }
+
+    @Override
+    public String toString() {
+        return getName() + this.paramMap;
+    }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerSchemeFactory.java
similarity index 60%
copy from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
copy to httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerSchemeFactory.java
index aeac33fa3..f05f1be14 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/BasicTestAuthenticator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerSchemeFactory.java
@@ -25,31 +25,32 @@
  *
  */
 
-package org.apache.hc.client5.testing;
+package org.apache.hc.client5.http.impl.auth;
 
-import java.util.Objects;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeFactory;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.protocol.HttpContext;
 
-import org.apache.hc.client5.testing.auth.Authenticator;
-import org.apache.hc.core5.net.URIAuthority;
-
-public class BasicTestAuthenticator implements Authenticator {
-
-    private final String userToken;
-    private final String realm;
+/**
+ * {@link AuthSchemeFactory} implementation that creates and initializes
+ * {@link BearerScheme} instances.
+ *
+ * @since 5.3
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public class BearerSchemeFactory implements AuthSchemeFactory {
 
-    public BasicTestAuthenticator(final String userToken, final String realm) {
-        this.userToken = userToken;
-        this.realm = realm;
-    }
+    public static final BearerSchemeFactory INSTANCE = new BearerSchemeFactory();
 
-    @Override
-    public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
-        return Objects.equals(userToken, credentials);
+    public BearerSchemeFactory() {
+        super();
     }
 
     @Override
-    public String getRealm(final URIAuthority authority, final String requestUri) {
-        return realm;
+    public AuthScheme create(final HttpContext context) {
+        return new BearerScheme();
     }
 
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
index b8d0d1037..96c67d96a 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
@@ -65,6 +65,7 @@ import org.apache.hc.client5.http.impl.IdleConnectionEvictor;
 import org.apache.hc.client5.http.impl.NoopUserTokenHandler;
 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.BearerSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
@@ -945,6 +946,7 @@ public class HttpClientBuilder {
             authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeFactory>create()
                 .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
                 .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
+                .register(StandardAuthScheme.BEARER, BearerSchemeFactory.INSTANCE)
                 .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE)
                 .register(StandardAuthScheme.SPNEGO, SPNegoSchemeFactory.DEFAULT)
                 .register(StandardAuthScheme.KERBEROS, KerberosSchemeFactory.DEFAULT)
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
index de14d7c48..5b72aa445 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestCredentials.java
@@ -103,6 +103,34 @@ public class TestCredentials {
         Assertions.assertEquals(creds1, creds3);
     }
 
+    @Test
+    public void tesBearerTokenBasics() {
+        final BearerToken creds1 = new BearerToken("token of some sort");
+        Assertions.assertEquals("token of some sort", creds1.getToken());
+    }
+
+    @Test
+    public void testBearerTokenHashCode() {
+        final BearerToken creds1 = new BearerToken("token of some sort");
+        final BearerToken creds2 = new BearerToken("another token of some sort");
+        final BearerToken creds3 = new BearerToken("token of some sort");
+
+        Assertions.assertTrue(creds1.hashCode() == creds1.hashCode());
+        Assertions.assertTrue(creds1.hashCode() != creds2.hashCode());
+        Assertions.assertTrue(creds1.hashCode() == creds3.hashCode());
+    }
+
+    @Test
+    public void testBearerTokenEquals() {
+        final BearerToken creds1 = new BearerToken("token of some sort");
+        final BearerToken creds2 = new BearerToken("another token of some sort");
+        final BearerToken creds3 = new BearerToken("token of some sort");
+
+        Assertions.assertEquals(creds1, creds1);
+        Assertions.assertNotEquals(creds1, creds2);
+        Assertions.assertEquals(creds1, creds3);
+    }
+
     @Test
     public void testNTCredentialsHashCode() {
         final NTCredentials creds1 = new NTCredentials(
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java
new file mode 100644
index 000000000..420475baa
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java
@@ -0,0 +1,104 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.auth;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.hc.client5.http.auth.AuthChallenge;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.BearerToken;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Bearer authentication test cases.
+ */
+public class TestBearerScheme {
+
+    @Test
+    public void testBearerAuthenticationEmptyChallenge() throws Exception {
+        final AuthChallenge authChallenge = new AuthChallenge(ChallengeType.TARGET, "BEARER");
+        final AuthScheme authscheme = new BearerScheme();
+        authscheme.processChallenge(authChallenge, null);
+        Assertions.assertNull(authscheme.getRealm());
+    }
+
+    @Test
+    public void testBearerAuthentication() throws Exception {
+        final AuthChallenge authChallenge = new AuthChallenge(ChallengeType.TARGET, "Bearer",
+                new BasicNameValuePair("realm", "test"));
+
+        final AuthScheme authscheme = new BearerScheme();
+        authscheme.processChallenge(authChallenge, null);
+
+        final HttpHost host  = new HttpHost("somehost", 80);
+        final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
+                .add(new AuthScope(host, "test", null), new BearerToken("some token"))
+                .build();
+
+        final HttpRequest request = new BasicHttpRequest("GET", "/");
+        Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
+        authscheme.generateAuthResponse(host, request, null);
+
+        Assertions.assertEquals("test", authscheme.getRealm());
+        Assertions.assertTrue(authscheme.isChallengeComplete());
+        Assertions.assertFalse(authscheme.isConnectionBased());
+    }
+
+    @Test
+    public void testSerialization() throws Exception {
+        final AuthChallenge authChallenge = new AuthChallenge(ChallengeType.TARGET, "Bearer",
+                new BasicNameValuePair("realm", "test"),
+                new BasicNameValuePair("code", "read"));
+
+        final AuthScheme authscheme = new BearerScheme();
+        authscheme.processChallenge(authChallenge, null);
+
+        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        final ObjectOutputStream out = new ObjectOutputStream(buffer);
+        out.writeObject(authscheme);
+        out.flush();
+        final byte[] raw = buffer.toByteArray();
+        final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw));
+        final BearerScheme authcheme2 = (BearerScheme) in.readObject();
+
+        Assertions.assertEquals(authcheme2.getName(), authcheme2.getName());
+        Assertions.assertEquals(authcheme2.getRealm(), authcheme2.getRealm());
+        Assertions.assertEquals(authcheme2.isChallengeComplete(), authcheme2.isChallengeComplete());
+    }
+
+}


[httpcomponents-client] 03/06: Normalize scheme name in AuthScope

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 3a8beba7b607111b1e95f8ff3c4758e777611b08
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Nov 26 23:12:59 2022 +0100

    Normalize scheme name in AuthScope
---
 .../java/org/apache/hc/client5/http/auth/AuthScope.java  | 16 +++++-----------
 .../org/apache/hc/client5/http/auth/TestAuthScope.java   |  4 ++--
 .../impl/auth/TestSystemDefaultCredentialsProvider.java  | 16 ++++++++++------
 3 files changed, 17 insertions(+), 19 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthScope.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthScope.java
index 312e5b861..35c309b3c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthScope.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthScope.java
@@ -76,7 +76,7 @@ public class AuthScope {
         this.host = host != null ? host.toLowerCase(Locale.ROOT) : null;
         this.port = port >= 0 ? port: -1;
         this.realm = realm;
-        this.schemeName = schemeName;
+        this.schemeName = schemeName != null ? schemeName.toUpperCase(Locale.ROOT) : null;
     }
 
     /**
@@ -99,7 +99,7 @@ public class AuthScope {
         this.host = origin.getHostName().toLowerCase(Locale.ROOT);
         this.port = origin.getPort() >= 0 ? origin.getPort() : -1;
         this.realm = realm;
-        this.schemeName = schemeName;
+        this.schemeName = schemeName != null ? schemeName.toUpperCase(Locale.ROOT) : null;
     }
 
     /**
@@ -167,8 +167,7 @@ public class AuthScope {
      */
     public int match(final AuthScope that) {
         int factor = 0;
-        if (Objects.equals(toNullSafeLowerCase(this.schemeName),
-                             toNullSafeLowerCase(that.schemeName))) {
+        if (Objects.equals(this.schemeName, that.schemeName)) {
             factor += 1;
         } else {
             if (this.schemeName != null && that.schemeName != null) {
@@ -217,8 +216,7 @@ public class AuthScope {
                     && Objects.equals(this.host, that.host)
                     && this.port == that.port
                     && Objects.equals(this.realm, that.realm)
-                    && Objects.equals(toNullSafeLowerCase(this.schemeName),
-                                        toNullSafeLowerCase(that.schemeName));
+                    && Objects.equals(this.schemeName, that.schemeName);
         }
         return false;
     }
@@ -230,14 +228,10 @@ public class AuthScope {
         hash = LangUtils.hashCode(hash, this.host);
         hash = LangUtils.hashCode(hash, this.port);
         hash = LangUtils.hashCode(hash, this.realm);
-        hash = LangUtils.hashCode(hash, toNullSafeLowerCase(this.schemeName));
+        hash = LangUtils.hashCode(hash, this.schemeName);
         return hash;
     }
 
-    private String toNullSafeLowerCase(final String str) {
-        return str != null ? str.toLowerCase(Locale.ROOT) : null;
-    }
-
     @Override
     public String toString() {
         final StringBuilder buffer = new StringBuilder();
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestAuthScope.java b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestAuthScope.java
index 61a68a8fe..b7b72b7b4 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestAuthScope.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/auth/TestAuthScope.java
@@ -38,12 +38,12 @@ public class TestAuthScope {
     @Test
     public void testBasics() {
         final AuthScope authscope = new AuthScope("http", "somehost", 80, "somerealm", "SomeScheme");
-        Assertions.assertEquals("SomeScheme", authscope.getSchemeName());
+        Assertions.assertEquals("SOMESCHEME", authscope.getSchemeName());
         Assertions.assertEquals("http", authscope.getProtocol());
         Assertions.assertEquals("somehost", authscope.getHost());
         Assertions.assertEquals(80, authscope.getPort());
         Assertions.assertEquals("somerealm", authscope.getRealm());
-        Assertions.assertEquals("SomeScheme 'somerealm' http://somehost:80", authscope.toString());
+        Assertions.assertEquals("SOMESCHEME 'somerealm' http://somehost:80", authscope.toString());
     }
 
     @Test
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
index 265b4c980..d1fa6ca98 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
@@ -31,6 +31,7 @@ import java.net.Authenticator.RequestorType;
 import java.net.InetAddress;
 import java.net.PasswordAuthentication;
 import java.net.URL;
+import java.util.Locale;
 
 import org.apache.hc.client5.http.auth.AuthScope;
 import org.apache.hc.client5.http.auth.Credentials;
@@ -99,9 +100,11 @@ public class TestSystemDefaultCredentialsProvider {
         final Credentials receivedCredentials =
             new SystemDefaultCredentialsProvider().getCredentials(authScope, coreContext);
 
-        Mockito.verify(authenticatorDelegate).getPasswordAuthentication(PROXY_HOST1, null, PROXY_PORT1, PROXY_PROTOCOL1,
-                                                                        PROMPT1, StandardAuthScheme.BASIC, httpRequestUrl,
-                                                                        RequestorType.SERVER);
+        Mockito.verify(authenticatorDelegate).getPasswordAuthentication(
+                PROXY_HOST1, null, PROXY_PORT1, PROXY_PROTOCOL1,
+                PROMPT1, StandardAuthScheme.BASIC.toUpperCase(Locale.ROOT),
+                httpRequestUrl,
+                RequestorType.SERVER);
         Assertions.assertNotNull(receivedCredentials);
         Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName());
     }
@@ -116,9 +119,10 @@ public class TestSystemDefaultCredentialsProvider {
         final Credentials receivedCredentials =
             new SystemDefaultCredentialsProvider().getCredentials(authScope, null);
 
-        Mockito.verify(authenticatorDelegate).getPasswordAuthentication(PROXY_HOST1, null, PROXY_PORT1, PROXY_PROTOCOL1,
-                                                                        PROMPT1, StandardAuthScheme.BASIC, null,
-                                                                        RequestorType.SERVER);
+        Mockito.verify(authenticatorDelegate).getPasswordAuthentication(
+                PROXY_HOST1, null, PROXY_PORT1, PROXY_PROTOCOL1,
+                PROMPT1, StandardAuthScheme.BASIC.toUpperCase(Locale.ROOT), null,
+                RequestorType.SERVER);
         Assertions.assertNotNull(receivedCredentials);
         Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName());
     }


[httpcomponents-client] 04/06: Made authenticating decorators capable of supporting different authentication schemes

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit d8212aff025a71f582a9dbd91e66dec244d95323
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Nov 27 15:04:24 2022 +0100

    Made authenticating decorators capable of supporting different authentication schemes
---
 .../async/AuthenticatingAsyncDecorator.java        | 28 +++++---
 .../auth/AbstractAuthenticationHandler.java        | 77 ++++++++++++++++++++++
 ...enExtractor.java => AuthenticationHandler.java} | 34 +++-------
 .../testing/auth/BasicAuthTokenExtractor.java      |  6 +-
 ...ractor.java => BasicAuthenticationHandler.java} | 35 ++++------
 .../testing/classic/AuthenticatingDecorator.java   | 28 ++++++--
 6 files changed, 144 insertions(+), 64 deletions(-)

diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
index cad07b812..f0bff871d 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java
@@ -28,12 +28,13 @@ package org.apache.hc.client5.testing.async;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.testing.auth.AuthenticationHandler;
 import org.apache.hc.client5.testing.auth.Authenticator;
-import org.apache.hc.client5.testing.auth.BasicAuthTokenExtractor;
+import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.Header;
@@ -44,6 +45,7 @@ import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.CapacityChannel;
@@ -58,15 +60,24 @@ import org.apache.hc.core5.util.Args;
 public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler {
 
     private final AsyncServerExchangeHandler exchangeHandler;
+    private final AuthenticationHandler<String> authenticationHandler;
     private final Authenticator authenticator;
     private final AtomicReference<AsyncResponseProducer> responseProducerRef;
-    private final BasicAuthTokenExtractor authTokenExtractor;
 
-    public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler, final Authenticator authenticator) {
+    /**
+     * @since 5.3
+     */
+    public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler,
+                                        final AuthenticationHandler<String> authenticationHandler,
+                                        final Authenticator authenticator) {
         this.exchangeHandler = Args.notNull(exchangeHandler, "Request handler");
+        this.authenticationHandler = Args.notNull(authenticationHandler, "Authentication handler");
         this.authenticator = Args.notNull(authenticator, "Authenticator");
         this.responseProducerRef = new AtomicReference<>();
-        this.authTokenExtractor = new BasicAuthTokenExtractor();
+    }
+
+    public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler, final Authenticator authenticator) {
+        this(exchangeHandler, new BasicAuthenticationHandler(), authenticator);
     }
 
     protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
@@ -79,7 +90,7 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler
             final ResponseChannel responseChannel,
             final HttpContext context) throws HttpException, IOException {
         final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
-        final String challengeResponse = h != null ? authTokenExtractor.extract(h.getValue()) : null;
+        final String challengeResponse = h != null ? authenticationHandler.extractAuthToken(h.getValue()) : null;
 
         final URIAuthority authority = request.getAuthority();
         final String requestUri = request.getRequestUri();
@@ -96,8 +107,9 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler
         } else {
             final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
             final String realm = authenticator.getRealm(authority, requestUri);
-            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=\"" + realm + "\"");
-
+            final String challenge = authenticationHandler.challenge(
+                    realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null);
+            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge);
             customizeUnauthorizedResponse(unauthorized);
 
             final AsyncResponseProducer responseProducer = new BasicResponseProducer(
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
new file mode 100644
index 000000000..7ba77f3b4
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java
@@ -0,0 +1,77 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.testing.auth;
+
+import java.util.List;
+
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.ProtocolException;
+
+abstract class AbstractAuthenticationHandler implements AuthenticationHandler<String> {
+
+    abstract String getSchemeName();
+
+    @Override
+    public final String challenge(final List<NameValuePair> params) {
+        final StringBuilder buf = new StringBuilder();
+        buf.append(getSchemeName());
+        if (params != null && params.size() > 0) {
+            buf.append(" ");
+            for (int i = 0; i < params.size(); i++) {
+                if (i > 0) {
+                    buf.append(", ");
+                }
+                final NameValuePair param = params.get(i);
+                buf.append(param.getName()).append("=\"").append(param.getValue()).append("\"");
+            }
+        }
+        return buf.toString();
+    }
+
+    abstract String decodeChallenge(String challenge) throws IllegalArgumentException;
+
+    public final String extractAuthToken(final String challengeResponse) throws HttpException {
+        final int i = challengeResponse.indexOf(' ');
+        if (i == -1) {
+            throw new ProtocolException("Invalid " + getSchemeName() + " challenge response");
+        }
+        final String schemeName = challengeResponse.substring(0, i);
+        if (schemeName.equalsIgnoreCase(getSchemeName())) {
+            final String s = challengeResponse.substring(i + 1).trim();
+            try {
+                return decodeChallenge(s);
+            } catch (final IllegalArgumentException ex) {
+                throw new ProtocolException("Malformed " + getSchemeName() + " credentials");
+            }
+        } else {
+            throw new ProtocolException("Unexpected challenge type");
+        }
+    }
+
+}
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthenticationHandler.java
similarity index 50%
copy from httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
copy to httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthenticationHandler.java
index b07dc9ccf..644c29729 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthenticationHandler.java
@@ -27,34 +27,18 @@
 
 package org.apache.hc.client5.testing.auth;
 
-import java.nio.charset.StandardCharsets;
+import java.util.List;
 
-import org.apache.hc.client5.http.utils.Base64;
-import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.NameValuePair;
 
-public class BasicAuthTokenExtractor {
+/**
+ * @since 5.3
+ */
+public interface AuthenticationHandler<T> {
+
+    String challenge(List<NameValuePair> params);
 
-    public String extract(final String challengeResponse) throws HttpException {
-        if (challengeResponse != null) {
-            final int i = challengeResponse.indexOf(' ');
-            if (i == -1) {
-                throw new ProtocolException("Invalid challenge response: " + challengeResponse);
-            }
-            final String schemeName = challengeResponse.substring(0, i);
-            if (schemeName.equalsIgnoreCase(StandardAuthScheme.BASIC)) {
-                final String s = challengeResponse.substring(i + 1).trim();
-                try {
-                    final byte[] credsRaw = s.getBytes(StandardCharsets.US_ASCII);
-                    final Base64 codec = new Base64();
-                    return new String(codec.decode(credsRaw), StandardCharsets.US_ASCII);
-                } catch (final IllegalArgumentException ex) {
-                    throw new ProtocolException("Malformed Basic credentials");
-                }
-            }
-        }
-        return null;
-    }
+    T extractAuthToken(String challengeResponse) throws HttpException;
 
 }
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
index b07dc9ccf..1ea4ab3c5 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
@@ -29,11 +29,15 @@ package org.apache.hc.client5.testing.auth;
 
 import java.nio.charset.StandardCharsets;
 
-import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.ProtocolException;
 
+/**
+ * @deprecated Use {@link BasicAuthenticationHandler}.
+ */
+@Deprecated
 public class BasicAuthTokenExtractor {
 
     public String extract(final String challengeResponse) throws HttpException {
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
similarity index 55%
copy from httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
copy to httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
index b07dc9ccf..c77e83349 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java
@@ -29,32 +29,21 @@ package org.apache.hc.client5.testing.auth;
 
 import java.nio.charset.StandardCharsets;
 
-import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.client5.http.utils.Base64;
 
-public class BasicAuthTokenExtractor {
+public class BasicAuthenticationHandler extends AbstractAuthenticationHandler {
+
+    @Override
+    String getSchemeName() {
+        return StandardAuthScheme.BASIC;
+    }
 
-    public String extract(final String challengeResponse) throws HttpException {
-        if (challengeResponse != null) {
-            final int i = challengeResponse.indexOf(' ');
-            if (i == -1) {
-                throw new ProtocolException("Invalid challenge response: " + challengeResponse);
-            }
-            final String schemeName = challengeResponse.substring(0, i);
-            if (schemeName.equalsIgnoreCase(StandardAuthScheme.BASIC)) {
-                final String s = challengeResponse.substring(i + 1).trim();
-                try {
-                    final byte[] credsRaw = s.getBytes(StandardCharsets.US_ASCII);
-                    final Base64 codec = new Base64();
-                    return new String(codec.decode(credsRaw), StandardCharsets.US_ASCII);
-                } catch (final IllegalArgumentException ex) {
-                    throw new ProtocolException("Malformed Basic credentials");
-                }
-            }
-        }
-        return null;
+    @Override
+    String decodeChallenge(final String challenge) throws IllegalArgumentException {
+        final byte[] bytes = challenge.getBytes(StandardCharsets.US_ASCII);
+        final Base64 codec = new Base64();
+        return new String(codec.decode(bytes), StandardCharsets.US_ASCII);
     }
 
 }
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
index ba5a8b945..e0598043e 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java
@@ -28,10 +28,11 @@
 package org.apache.hc.client5.testing.classic;
 
 import java.io.IOException;
+import java.util.Collections;
 
-import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.testing.auth.AuthenticationHandler;
 import org.apache.hc.client5.testing.auth.Authenticator;
-import org.apache.hc.client5.testing.auth.BasicAuthTokenExtractor;
+import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.Header;
@@ -42,6 +43,7 @@ import org.apache.hc.core5.http.io.HttpServerRequestHandler;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.net.URIAuthority;
 import org.apache.hc.core5.util.Args;
@@ -49,13 +51,23 @@ import org.apache.hc.core5.util.Args;
 public class AuthenticatingDecorator implements HttpServerRequestHandler {
 
     private final HttpServerRequestHandler requestHandler;
+    private final AuthenticationHandler<String> authenticationHandler;
     private final Authenticator authenticator;
-    private final BasicAuthTokenExtractor authTokenExtractor;
 
-    public AuthenticatingDecorator(final HttpServerRequestHandler requestHandler, final Authenticator authenticator) {
+    /**
+     * @since 5.3
+     */
+    public AuthenticatingDecorator(final HttpServerRequestHandler requestHandler,
+                                   final AuthenticationHandler<String> authenticationHandler,
+                                   final Authenticator authenticator) {
         this.requestHandler = Args.notNull(requestHandler, "Request handler");
+        this.authenticationHandler = Args.notNull(authenticationHandler, "Authentication handler");
         this.authenticator = Args.notNull(authenticator, "Authenticator");
-        this.authTokenExtractor = new BasicAuthTokenExtractor();
+    }
+
+    public AuthenticatingDecorator(final HttpServerRequestHandler requestHandler,
+                                   final Authenticator authenticator) {
+        this(requestHandler, new BasicAuthenticationHandler(), authenticator);
     }
 
     protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) {
@@ -67,7 +79,7 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler {
             final ResponseTrigger responseTrigger,
             final HttpContext context) throws HttpException, IOException {
         final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
-        final String challengeResponse = h != null ? authTokenExtractor.extract(h.getValue()) : null;
+        final String challengeResponse = h != null ? authenticationHandler.extractAuthToken(h.getValue()) : null;
 
         final URIAuthority authority = request.getAuthority();
         final String requestUri = request.getRequestUri();
@@ -84,7 +96,9 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler {
         } else {
             final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
             final String realm = authenticator.getRealm(authority, requestUri);
-            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=\"" + realm + "\"");
+            final String challenge = authenticationHandler.challenge(
+                    realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null);
+            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge);
             customizeUnauthorizedResponse(unauthorized);
             if (unauthorized.getEntity() == null) {
                 unauthorized.setEntity(new StringEntity("Unauthorized"));


[httpcomponents-client] 02/06: Removed references in specific RFCs

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 8c53de2f8bd3c174e33d2dbd5242d6300c30e152
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Nov 26 23:07:30 2022 +0100

    Removed references in specific RFCs
---
 .../java/org/apache/hc/client5/http/auth/StandardAuthScheme.java    | 6 +++---
 .../main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java | 2 +-
 .../java/org/apache/hc/client5/http/impl/auth/DigestScheme.java     | 5 ++---
 3 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
index feb7e6c51..51371cc7b 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
@@ -39,13 +39,13 @@ public final class StandardAuthScheme {
     }
 
     /**
-     * Basic authentication scheme as defined in RFC 2617 (considered inherently
-     * insecure without transport encryption, but most widely supported).
+     * Basic authentication scheme (considered inherently insecure without transport encryption,
+     * but most widely supported).
      */
     public static final String BASIC = "Basic";
 
     /**
-     * Digest authentication scheme as defined in RFC 2617.
+     * Digest authentication scheme.
      */
     public static final String DIGEST = "Digest";
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
index d6d0d027c..03bf73e45 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
@@ -61,7 +61,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Basic authentication scheme as defined in RFC 2617.
+ * Basic authentication scheme.
  *
  * @since 4.0
  */
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
index f73f59055..fb8b97d92 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
@@ -71,7 +71,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Digest authentication scheme as defined in RFC 2617.
+ * Digest authentication scheme.
  * Both MD5 (default) and MD5-sess are supported.
  * Currently only qop=auth or no qop is supported. qop=auth-int
  * is unsupported. If auth and auth-int are provided, auth is
@@ -443,8 +443,7 @@ public class DigestScheme implements AuthScheme, Serializable {
     }
 
     /**
-     * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long
-     * {@code String} according to RFC 2617.
+     * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long string.
      *
      * @param binaryData array containing the digest
      * @return encoded MD5, or {@code null} if encoding failed