Open micheljung opened 5 years ago
Here comes my workaround. Specify a class:
import org.apache.http.FormattedHeader;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.impl.client.TargetAuthenticationStrategy;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
import org.apache.http.util.CharArrayBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Workaround for <a href="https://jira.apache.org/jira/browse/HTTPCLIENT-1489">HTTPCLIENT-1489</a>.
*/
public class HttpClient1489TargetAuthenticationStrategy extends TargetAuthenticationStrategy {
private final String challengeName;
public HttpClient1489TargetAuthenticationStrategy(String challengeName) {
this.challengeName = challengeName;
}
/**
* Generates a map of challenge auth-scheme => Header entries.
*
* @return map: key=lower-cased auth-scheme name, value=Header that contains the challenge
*/
@Override
public Map<String, Header> getChallenges(
final HttpHost authhost,
final HttpResponse response,
final HttpContext context
) throws MalformedChallengeException {
Args.notNull(response, "HTTP response");
final Header[] headers = filterChallenge(response.getHeaders(AUTH.WWW_AUTH), challengeName);
final Map<String, Header> map = new HashMap<>(headers.length);
for (final Header header : headers) {
final CharArrayBuffer buffer;
int pos;
if (header instanceof FormattedHeader) {
buffer = ((FormattedHeader) header).getBuffer();
pos = ((FormattedHeader) header).getValuePos();
} else {
final String s = header.getValue();
if (s == null) {
throw new MalformedChallengeException("Header value is null");
}
buffer = new CharArrayBuffer(s.length());
buffer.append(s);
pos = 0;
}
while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
pos++;
}
final int beginIndex = pos;
while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
pos++;
}
final int endIndex = pos;
final String s = buffer.substring(beginIndex, endIndex);
map.put(s.toLowerCase(Locale.ROOT), header);
}
return map;
}
/**
* Removes all but the specified {@code WWW-Authenticate} challenge.
* <p>
* For instance, the header:
* <pre>
* WWW-Authenticate: X-MobileMe-AuthToken realm="Newcastle", Basic realm="Newcastle"
* </pre>
* becomes:
* <pre>
* WWW-Authenticate: X-MobileMe-AuthToken realm="Newcastle"
* </pre>
* if this class has been instantiated with "X-MobileMe-AuthToken" or:
* <pre>
* WWW-Authenticate: Basic realm="Newcastle
* </pre>
* if this class has been instantiated with "Basic". An exception is thrown if the specified
* challenge could not be found.
* </p>
*/
private Header[] filterChallenge(Header[] headers) {
// CAVEAT: Calling header.getElements() here is prone to error if the base64 string ends with "="
return Arrays.stream(headers)
.map(header -> Arrays.stream(header.getValue().split(","))
.map(String::trim)
.filter(headerElement -> headerElement
.toLowerCase(Locale.US)
.startsWith(challengeName.toLowerCase(Locale.US)))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("There must be exactly one challenge with name '"
+ challengeName + "' in headers: " + Arrays.toString(headers)))
)
.map(headerElement -> new BasicHeader(AUTH.WWW_AUTH, headerElement))
.toArray(Header[]::new);
}
}
And create your own HttpClient
, with the customized authentication strategy set:
private static HttpClient buildHttpClient() {
HttpClientBuilder builder = HttpClientBuilder.create();
Lookup<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();
builder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(null, -1, null), new NullCredentials());
builder.setDefaultCredentialsProvider(credentialsProvider);
builder.setTargetAuthenticationStrategy(new HttpClient1489TargetAuthenticationStrategy("negotiate"));
return builder.build();
}
private static class NullCredentials implements Credentials {
@Override
public Principal getUserPrincipal() {
return null;
}
@Override
public String getPassword() {
return null;
}
}
Since this project uses Apache's httpclient 4.3.3, it suffers from https://jira.apache.org/jira/browse/HTTPCLIENT-1489
The issue has been resolved in httpclient 5.0 Alpha1 but users can't just upgrade because the API is incompatible.
If I come up with a workaround, I'll post it here or create a pull request.