-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for per-request authentication to Jsoup.connect (#2046)
Added support for per-request authentication Uses the multi-version support so that in Java versions that support it (9+), an authenticator is set via `java.net.HttpURLConnection.setAuthenticator()`. On Java 8, we set the system-wide default authenticator, and use ThreadLocals to enable per-request authenticators. Also adds tests for HTTP and HTTPS server and proxy basic authentication.
- Loading branch information
Showing
14 changed files
with
708 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package org.jsoup.helper; | ||
|
||
import javax.annotation.Nullable; | ||
import java.lang.reflect.Constructor; | ||
import java.net.Authenticator; | ||
import java.net.HttpURLConnection; | ||
import java.net.PasswordAuthentication; | ||
|
||
/** | ||
Handles per request Authenticator-based authentication. Loads the class `org.jsoup.helper.RequestAuthHandler` if | ||
per-request Authenticators are supported (Java 9+), or installs a system-wide Authenticator that delegates to a request | ||
ThreadLocal. | ||
*/ | ||
class AuthenticationHandler extends Authenticator { | ||
static final int MaxAttempts = 5; // max authentication attempts per request. allows for multiple auths (e.g. proxy and server) in one request, but saves otherwise 20 requests if credentials are incorrect. | ||
static AuthShim handler; | ||
|
||
static { | ||
try { | ||
//noinspection unchecked | ||
Class<AuthShim> perRequestClass = (Class<AuthShim>) Class.forName("org.jsoup.helper.RequestAuthHandler"); | ||
Constructor<AuthShim> constructor = perRequestClass.getConstructor(); | ||
handler = constructor.newInstance(); | ||
} catch (ClassNotFoundException e) { | ||
handler = new GlobalHandler(); | ||
} catch (Exception e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
@Nullable RequestAuthenticator auth; | ||
int attemptCount = 0; | ||
|
||
AuthenticationHandler() {} | ||
|
||
AuthenticationHandler(RequestAuthenticator auth) { | ||
this.auth = auth; | ||
} | ||
|
||
/** | ||
Authentication callback, called by HttpURLConnection - either as system-wide default (Java 8) or per HttpURLConnection (Java 9+) | ||
* @return credentials, or null if not attempting to auth. | ||
*/ | ||
@Nullable @Override public final PasswordAuthentication getPasswordAuthentication() { | ||
AuthenticationHandler delegate = handler.get(this); | ||
if (delegate == null) return null; // this request has no auth handler | ||
delegate.attemptCount++; | ||
// if the password returned fails, Java will repeatedly retry the request with a new password auth hit (because | ||
// it may be an interactive prompt, and the user could eventually get it right). But in Jsoup's context, the | ||
// auth will either be correct or not, so just abandon | ||
if (delegate.attemptCount > MaxAttempts) | ||
return null; | ||
if (delegate.auth == null) | ||
return null; // detached - would have been the Global Authenticator (not a delegate) | ||
|
||
RequestAuthenticator.Context ctx = new RequestAuthenticator.Context( | ||
this.getRequestingURL(), this.getRequestorType(), this.getRequestingPrompt()); | ||
return delegate.auth.authenticate(ctx); | ||
} | ||
|
||
interface AuthShim { | ||
void enable(RequestAuthenticator auth, HttpURLConnection con); | ||
|
||
void remove(); | ||
|
||
@Nullable AuthenticationHandler get(AuthenticationHandler helper); | ||
} | ||
|
||
/** | ||
On Java 8 we install a system-wide Authenticator, which pulls the delegating Auth from a ThreadLocal pool. | ||
*/ | ||
static class GlobalHandler implements AuthShim { | ||
static ThreadLocal<AuthenticationHandler> authenticators = new ThreadLocal<>(); | ||
static { | ||
Authenticator.setDefault(new AuthenticationHandler()); | ||
} | ||
|
||
@Override public void enable(RequestAuthenticator auth, HttpURLConnection con) { | ||
authenticators.set(new AuthenticationHandler(auth)); | ||
} | ||
|
||
@Override public void remove() { | ||
authenticators.remove(); | ||
} | ||
|
||
@Override public AuthenticationHandler get(AuthenticationHandler helper) { | ||
return authenticators.get(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.