Fixes #5845 - Use UTF-8 encoding for client basic auth if requested. (#5847)

* Fixes #5845 - Use UTF-8 encoding for client basic auth if requested.

* Introduced get/setCharset in BasicAuthenticator on server-side.
* Looking for the "charset" parameter on the client-side, and if there, use it.
* Added test case.
* Code cleanups.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-01-12 11:26:33 +01:00 committed by GitHub
parent 17891673b1
commit 6e1cd862e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 27 deletions

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.client.util; package org.eclipse.jetty.client.util;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
@ -63,7 +64,9 @@ public class BasicAuthentication extends AbstractAuthentication
@Override @Override
public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context) public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
{ {
return new BasicResult(getURI(), headerInfo.getHeader(), user, password); String charsetParam = headerInfo.getParameter("charset");
Charset charset = charsetParam == null ? null : Charset.forName(charsetParam);
return new BasicResult(getURI(), headerInfo.getHeader(), user, password, charset);
} }
/** /**
@ -88,10 +91,17 @@ public class BasicAuthentication extends AbstractAuthentication
} }
public BasicResult(URI uri, HttpHeader header, String user, String password) public BasicResult(URI uri, HttpHeader header, String user, String password)
{
this(uri, header, user, password, StandardCharsets.ISO_8859_1);
}
public BasicResult(URI uri, HttpHeader header, String user, String password, Charset charset)
{ {
this.uri = uri; this.uri = uri;
this.header = header; this.header = header;
byte[] authBytes = (user + ":" + password).getBytes(StandardCharsets.ISO_8859_1); if (charset == null)
charset = StandardCharsets.ISO_8859_1;
byte[] authBytes = (user + ":" + password).getBytes(charset);
this.value = "Basic " + Base64.getEncoder().encodeToString(authBytes); this.value = "Basic " + Base64.getEncoder().encodeToString(authBytes);
} }

View File

@ -22,6 +22,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -79,17 +81,25 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{ {
private String realm = "TestRealm"; private String realm = "TestRealm";
public void startBasic(final Scenario scenario, Handler handler) throws Exception public void startBasic(Scenario scenario, Handler handler) throws Exception
{ {
start(scenario, new BasicAuthenticator(), handler); startBasic(scenario, handler, null);
} }
public void startDigest(final Scenario scenario, Handler handler) throws Exception public void startBasic(Scenario scenario, Handler handler, Charset charset) throws Exception
{
BasicAuthenticator authenticator = new BasicAuthenticator();
if (charset != null)
authenticator.setCharset(charset);
start(scenario, authenticator, handler);
}
public void startDigest(Scenario scenario, Handler handler) throws Exception
{ {
start(scenario, new DigestAuthenticator(), handler); start(scenario, new DigestAuthenticator(), handler);
} }
private void start(final Scenario scenario, Authenticator authenticator, Handler handler) throws Exception private void start(Scenario scenario, Authenticator authenticator, Handler handler) throws Exception
{ {
server = new Server(); server = new Server();
File realmFile = MavenTestingUtils.getTestResourceFile("realm.properties"); File realmFile = MavenTestingUtils.getTestResourceFile("realm.properties");
@ -141,6 +151,16 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
testAuthentication(scenario, new BasicAuthentication(uri, ANY_REALM, "basic", "basic")); testAuthentication(scenario, new BasicAuthentication(uri, ANY_REALM, "basic", "basic"));
} }
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testBasicWithUTF8Password(Scenario scenario) throws Exception
{
startBasic(scenario, new EmptyServerHandler(), StandardCharsets.UTF_8);
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
testAuthentication(scenario, new BasicAuthentication(uri, realm, "basic_utf8", "\u20AC"));
}
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(ScenarioProvider.class) @ArgumentsSource(ScenarioProvider.class)
public void testDigestAuthentication(Scenario scenario) throws Exception public void testDigestAuthentication(Scenario scenario) throws Exception
@ -159,11 +179,11 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
testAuthentication(scenario, new DigestAuthentication(uri, ANY_REALM, "digest", "digest")); testAuthentication(scenario, new DigestAuthentication(uri, ANY_REALM, "digest", "digest"));
} }
private void testAuthentication(final Scenario scenario, Authentication authentication) throws Exception private void testAuthentication(Scenario scenario, Authentication authentication) throws Exception
{ {
AuthenticationStore authenticationStore = client.getAuthenticationStore(); AuthenticationStore authenticationStore = client.getAuthenticationStore();
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(1)); AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(1));
Request.Listener.Adapter requestListener = new Request.Listener.Adapter() Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{ {
@Override @Override
@ -247,7 +267,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort()); URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic")); client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3); CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Adapter requestListener = new Request.Listener.Adapter() Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{ {
@Override @Override
@ -286,7 +306,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort()); URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic")); client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3); CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Adapter requestListener = new Request.Listener.Adapter() Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{ {
@Override @Override
@ -314,7 +334,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{ {
startBasic(scenario, new EmptyServerHandler()); startBasic(scenario, new EmptyServerHandler());
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(2)); AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(2));
Request.Listener.Adapter requestListener = new Request.Listener.Adapter() Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{ {
@Override @Override
@ -386,7 +406,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
// Request without Authentication would cause a 401, // Request without Authentication would cause a 401,
// but the client will throw an exception trying to // but the client will throw an exception trying to
// send the credentials to the server. // send the credentials to the server.
final String cause = "thrown_explicitly_by_test"; String cause = "thrown_explicitly_by_test";
client.getAuthenticationStore().addAuthentication(new Authentication() client.getAuthenticationStore().addAuthentication(new Authentication()
{ {
@Override @Override
@ -402,7 +422,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
} }
}); });
final CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort()) client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme()) .scheme(scenario.getScheme())
.path("/secure") .path("/secure")

View File

@ -1,4 +1,5 @@
# Format is <user>:<password>,<roles> # Format is <user>:<password>,<roles>
basic:basic basic:basic
basic_utf8:\u20AC
digest:digest digest:digest
spnego_client:,admin spnego_client:,admin

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.security.authentication; package org.eclipse.jetty.security.authentication;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@ -34,28 +35,26 @@ import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Constraint;
/**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*/
public class BasicAuthenticator extends LoginAuthenticator public class BasicAuthenticator extends LoginAuthenticator
{ {
private Charset _charset;
public BasicAuthenticator() public Charset getCharset()
{ {
return _charset;
}
public void setCharset(Charset charset)
{
this._charset = charset;
} }
/**
* @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
*/
@Override @Override
public String getAuthMethod() public String getAuthMethod()
{ {
return Constraint.__BASIC_AUTH; return Constraint.__BASIC_AUTH;
} }
/**
* @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
*/
@Override @Override
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
{ {
@ -77,7 +76,10 @@ public class BasicAuthenticator extends LoginAuthenticator
if ("basic".equalsIgnoreCase(method)) if ("basic".equalsIgnoreCase(method))
{ {
credentials = credentials.substring(space + 1); credentials = credentials.substring(space + 1);
credentials = new String(Base64.getDecoder().decode(credentials), StandardCharsets.ISO_8859_1); Charset charset = getCharset();
if (charset == null)
charset = StandardCharsets.ISO_8859_1;
credentials = new String(Base64.getDecoder().decode(credentials), charset);
int i = credentials.indexOf(':'); int i = credentials.indexOf(':');
if (i > 0) if (i > 0)
{ {
@ -86,18 +88,20 @@ public class BasicAuthenticator extends LoginAuthenticator
UserIdentity user = login(username, password, request); UserIdentity user = login(username, password, request);
if (user != null) if (user != null)
{
return new UserAuthentication(getAuthMethod(), user); return new UserAuthentication(getAuthMethod(), user);
} }
} }
} }
} }
}
if (DeferredAuthentication.isDeferred(response)) if (DeferredAuthentication.isDeferred(response))
return Authentication.UNAUTHENTICATED; return Authentication.UNAUTHENTICATED;
response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"'); String value = "basic realm=\"" + _loginService.getName() + "\"";
Charset charset = getCharset();
if (charset != null)
value += ", charset=\"" + charset.name() + "\"";
response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), value);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return Authentication.SEND_CONTINUE; return Authentication.SEND_CONTINUE;
} }