* 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:
parent
17891673b1
commit
6e1cd862e4
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue