diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 4d0bacc664354621d9a81784e84eeaf742541fbd..fe59357ab829e6241be4232fdaf75fc87b6ecc45 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -16,6 +16,7 @@ Import-Package: org.eclipse.jgit.awtui,
  org.eclipse.jgit.transport,
  org.eclipse.jgit.treewalk,
  org.eclipse.jgit.treewalk.filter,
+ org.eclipse.jgit.util,
  org.kohsuke.args4j,
  org.kohsuke.args4j.spi
 Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index 281e2f3894321373106a775e29d3d553d549f6e0..7c74ee54807dd63b81b87488967213267c735621 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -55,6 +55,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.eclipse.jgit.util.CachedAuthenticator;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.ExampleMode;
@@ -215,8 +216,9 @@ private static void configureHttpProxy() throws MalformedURLException {
 			final int c = userpass.indexOf(':');
 			final String user = userpass.substring(0, c);
 			final String pass = userpass.substring(c + 1);
-			AwtAuthenticator.add(new AwtAuthenticator.CachedAuthentication(
-					proxyHost, proxyPort, user, pass));
+			CachedAuthenticator
+					.add(new CachedAuthenticator.CachedAuthentication(
+							proxyHost, proxyPort, user, pass));
 		}
 	}
 }
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java
index 995fe9a935d8923566b28a2e62c473a31c1f1e02..1d2f9d765c1ef10ce77341377225cc94523c8b70 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtAuthenticator.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2009, Google Inc.
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
  *
@@ -47,10 +48,7 @@
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
-import java.net.Authenticator;
 import java.net.PasswordAuthentication;
-import java.util.ArrayList;
-import java.util.Collection;
 
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
@@ -58,37 +56,17 @@
 import javax.swing.JPasswordField;
 import javax.swing.JTextField;
 
-/** Basic network prompt for username/password when using AWT. */
-public class AwtAuthenticator extends Authenticator {
-	private static final AwtAuthenticator me = new AwtAuthenticator();
+import org.eclipse.jgit.util.CachedAuthenticator;
 
+/** Basic network prompt for username/password when using AWT. */
+public class AwtAuthenticator extends CachedAuthenticator {
 	/** Install this authenticator implementation into the JVM. */
 	public static void install() {
-		setDefault(me);
-	}
-
-	/**
-	 * Add a cached authentication for future use.
-	 *
-	 * @param ca
-	 *            the information we should remember.
-	 */
-	public static void add(final CachedAuthentication ca) {
-		synchronized (me) {
-			me.cached.add(ca);
-		}
+		setDefault(new AwtAuthenticator());
 	}
 
-	private final Collection<CachedAuthentication> cached = new ArrayList<CachedAuthentication>();
-
 	@Override
-	protected PasswordAuthentication getPasswordAuthentication() {
-		for (final CachedAuthentication ca : cached) {
-			if (ca.host.equals(getRequestingHost())
-					&& ca.port == getRequestingPort())
-				return ca.toPasswordAuthentication();
-		}
-
+	protected PasswordAuthentication promptPasswordAuthentication() {
 		final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
 				GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
 				new Insets(0, 0, 0, 0), 0, 0);
@@ -150,48 +128,10 @@ protected PasswordAuthentication getPasswordAuthentication() {
 		if (JOptionPane.showConfirmDialog(null, panel,
 				"Authentication Required", JOptionPane.OK_CANCEL_OPTION,
 				JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
-			final CachedAuthentication ca = new CachedAuthentication(
-					getRequestingHost(), getRequestingPort(), username
-							.getText(), new String(password.getPassword()));
-			cached.add(ca);
-			return ca.toPasswordAuthentication();
+			return new PasswordAuthentication(username.getText(), password
+					.getPassword());
 		}
 
 		return null; // cancel
 	}
-
-	/** Authentication data to remember and reuse. */
-	public static class CachedAuthentication {
-		final String host;
-
-		final int port;
-
-		final String user;
-
-		final String pass;
-
-		/**
-		 * Create a new cached authentication.
-		 *
-		 * @param aHost
-		 *            system this is for.
-		 * @param aPort
-		 *            port number of the service.
-		 * @param aUser
-		 *            username at the service.
-		 * @param aPass
-		 *            password at the service.
-		 */
-		public CachedAuthentication(final String aHost, final int aPort,
-				final String aUser, final String aPass) {
-			host = aHost;
-			port = aPort;
-			user = aUser;
-			pass = aPass;
-		}
-
-		PasswordAuthentication toPasswordAuthentication() {
-			return new PasswordAuthentication(user, pass.toCharArray());
-		}
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
new file mode 100644
index 0000000000000000000000000000000000000000..6828185a8ac7ac8d0e937eaaf51ce77c59db7449
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Abstract authenticator which remembers prior authentications. */
+public abstract class CachedAuthenticator extends Authenticator {
+	private static final Collection<CachedAuthentication> cached = new CopyOnWriteArrayList<CachedAuthentication>();
+
+	/**
+	 * Add a cached authentication for future use.
+	 *
+	 * @param ca
+	 *            the information we should remember.
+	 */
+	public static void add(final CachedAuthentication ca) {
+		cached.add(ca);
+	}
+
+	@Override
+	protected final PasswordAuthentication getPasswordAuthentication() {
+		final String host = getRequestingHost();
+		final int port = getRequestingPort();
+		for (final CachedAuthentication ca : cached) {
+			if (ca.host.equals(host) && ca.port == port)
+				return ca.toPasswordAuthentication();
+		}
+		PasswordAuthentication pa = promptPasswordAuthentication();
+		if (pa != null) {
+			CachedAuthentication ca = new CachedAuthentication(host, port, pa
+					.getUserName(), new String(pa.getPassword()));
+			add(ca);
+			return ca.toPasswordAuthentication();
+		}
+		return null;
+	}
+
+	/**
+	 * Prompt for and request authentication from the end-user.
+	 *
+	 * @return the authentication data; null if the user canceled the request
+	 *         and does not want to continue.
+	 */
+	protected abstract PasswordAuthentication promptPasswordAuthentication();
+
+	/** Authentication data to remember and reuse. */
+	public static class CachedAuthentication {
+		final String host;
+
+		final int port;
+
+		final String user;
+
+		final String pass;
+
+		/**
+		 * Create a new cached authentication.
+		 *
+		 * @param aHost
+		 *            system this is for.
+		 * @param aPort
+		 *            port number of the service.
+		 * @param aUser
+		 *            username at the service.
+		 * @param aPass
+		 *            password at the service.
+		 */
+		public CachedAuthentication(final String aHost, final int aPort,
+				final String aUser, final String aPass) {
+			host = aHost;
+			port = aPort;
+			user = aUser;
+			pass = aPass;
+		}
+
+		PasswordAuthentication toPasswordAuthentication() {
+			return new PasswordAuthentication(user, pass.toCharArray());
+		}
+	}
+}