6829283: HTTP/Negotiate: Autheticator triggered again when user cancels the first one
Reviewed-by: chegar
--- a/jdk/src/share/classes/sun/net/www/protocol/http/spnego/NegotiateCallbackHandler.java Wed Mar 17 09:55:04 2010 +0800
+++ b/jdk/src/share/classes/sun/net/www/protocol/http/spnego/NegotiateCallbackHandler.java Thu Mar 18 18:26:37 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright 2005-2009 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright 2005-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -45,43 +45,50 @@
private String username;
private char[] password;
+ /**
+ * Authenticator asks for username and password in a single prompt,
+ * but CallbackHandler checks one by one. So, no matter which callback
+ * gets handled first, make sure Authenticator is only called once.
+ */
+ private boolean answered;
+
private final HttpCallerInfo hci;
public NegotiateCallbackHandler(HttpCallerInfo hci) {
this.hci = hci;
}
+ private void getAnswer() {
+ if (!answered) {
+ answered = true;
+ PasswordAuthentication passAuth =
+ Authenticator.requestPasswordAuthentication(
+ hci.host, hci.addr, hci.port, hci.protocol,
+ hci.prompt, hci.scheme, hci.url, hci.authType);
+ /**
+ * To be compatible with existing callback handler implementations,
+ * when the underlying Authenticator is canceled, username and
+ * password are assigned null. No exception is thrown.
+ */
+ if (passAuth != null) {
+ username = passAuth.getUserName();
+ password = passAuth.getPassword();
+ }
+ }
+ }
+
public void handle(Callback[] callbacks) throws
UnsupportedCallbackException, IOException {
for (int i=0; i<callbacks.length; i++) {
Callback callBack = callbacks[i];
if (callBack instanceof NameCallback) {
- if (username == null) {
- PasswordAuthentication passAuth =
- Authenticator.requestPasswordAuthentication(
- hci.host, hci.addr, hci.port, hci.protocol,
- hci.prompt, hci.scheme, hci.url, hci.authType);
- username = passAuth.getUserName();
- password = passAuth.getPassword();
- }
- NameCallback nameCallback =
- (NameCallback)callBack;
- nameCallback.setName(username);
-
+ getAnswer();
+ ((NameCallback)callBack).setName(username);
} else if (callBack instanceof PasswordCallback) {
- PasswordCallback passwordCallback =
- (PasswordCallback)callBack;
- if (password == null) {
- PasswordAuthentication passAuth =
- Authenticator.requestPasswordAuthentication(
- hci.host, hci.addr, hci.port, hci.protocol,
- hci.prompt, hci.scheme, hci.url, hci.authType);
- username = passAuth.getUserName();
- password = passAuth.getPassword();
- }
- passwordCallback.setPassword(password);
- Arrays.fill(password, ' ');
+ getAnswer();
+ ((PasswordCallback)callBack).setPassword(password);
+ if (password != null) Arrays.fill(password, ' ');
} else {
throw new UnsupportedCallbackException(callBack,
"Call back not supported");
--- a/jdk/test/sun/security/krb5/auto/HttpNegotiateServer.java Wed Mar 17 09:55:04 2010 +0800
+++ b/jdk/test/sun/security/krb5/auto/HttpNegotiateServer.java Thu Mar 18 18:26:37 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright 2009-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,8 +23,9 @@
/*
* @test
- * @bug 6578647
+ * @bug 6578647 6829283
* @summary Undefined requesting URL in java.net.Authenticator.getPasswordAuthentication()
+ * @summary HTTP/Negotiate: Authenticator triggered again when user cancels the first one
*/
import com.sun.net.httpserver.Headers;
@@ -35,6 +36,8 @@
import com.sun.net.httpserver.HttpPrincipal;
import com.sun.security.auth.module.Krb5LoginModule;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
@@ -79,6 +82,9 @@
// web page content
final static String CONTENT = "Hello, World!";
+ // For 6829283, count how many times the Authenticator is called.
+ static int count = 0;
+
// URLs for web test, proxy test. The proxy server is not a real proxy
// since it fakes the same content for any URL. :)
final static URL webUrl, proxyUrl;
@@ -134,6 +140,17 @@
}
}
+ /**
+ * This Authenticator knows nothing
+ */
+ static class KnowNothingAuthenticator extends java.net.Authenticator {
+ @Override
+ public PasswordAuthentication getPasswordAuthentication () {
+ HttpNegotiateServer.count++;
+ return null;
+ }
+ }
+
public static void main(String[] args)
throws Exception {
@@ -147,7 +164,6 @@
kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY);
kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST);
- KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp);
KDC.saveConfig(KRB5_CONF, kdcw, kdcp,
"default_keytab_name = " + KRB5_TAB,
"[domain_realm]",
@@ -157,6 +173,19 @@
System.setProperty("java.security.krb5.conf", KRB5_CONF);
Config.refresh();
+ KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp);
+
+ // Write a customized JAAS conf file, so that any kinit cache
+ // will be ignored.
+ System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
+ File f = new File(OneKDC.JAAS_CONF);
+ FileOutputStream fos = new FileOutputStream(f);
+ fos.write((
+ "com.sun.security.jgss.krb5.initiate {\n" +
+ " com.sun.security.auth.module.Krb5LoginModule required;\n};\n"
+ ).getBytes());
+ fos.close();
+ f.deleteOnExit();
HttpServer h1 = httpd(WEB_PORT, "Negotiate", false,
"HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB);
@@ -164,23 +193,21 @@
"HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB);
try {
-
- BufferedReader reader;
- java.net.Authenticator.setDefault(new KnowAllAuthenticator());
-
- reader = new BufferedReader(new InputStreamReader(
- webUrl.openConnection().getInputStream()));
- if (!reader.readLine().equals(CONTENT)) {
- throw new RuntimeException("Bad content");
+ Exception e1 = null, e2 = null;
+ try {
+ test6578647();
+ } catch (Exception e) {
+ e1 = e;
+ e.printStackTrace();
}
-
- reader = new BufferedReader(new InputStreamReader(
- proxyUrl.openConnection(
- new Proxy(Proxy.Type.HTTP,
- new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
- .getInputStream()));
- if (!reader.readLine().equals(CONTENT)) {
- throw new RuntimeException("Bad content");
+ try {
+ test6829283();
+ } catch (Exception e) {
+ e2 = e;
+ e.printStackTrace();
+ }
+ if (e1 != null || e2 != null) {
+ throw new RuntimeException("Test error");
}
} finally {
// Must stop. Seems there's no HttpServer.startAsDaemon()
@@ -189,6 +216,40 @@
}
}
+ static void test6578647() throws Exception {
+ BufferedReader reader;
+ java.net.Authenticator.setDefault(new KnowAllAuthenticator());
+
+ reader = new BufferedReader(new InputStreamReader(
+ webUrl.openConnection().getInputStream()));
+ if (!reader.readLine().equals(CONTENT)) {
+ throw new RuntimeException("Bad content");
+ }
+
+ reader = new BufferedReader(new InputStreamReader(
+ proxyUrl.openConnection(
+ new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
+ .getInputStream()));
+ if (!reader.readLine().equals(CONTENT)) {
+ throw new RuntimeException("Bad content");
+ }
+ }
+
+ static void test6829283() throws Exception {
+ BufferedReader reader;
+ java.net.Authenticator.setDefault(new KnowNothingAuthenticator());
+ try {
+ new BufferedReader(new InputStreamReader(
+ webUrl.openConnection().getInputStream()));
+ } catch (IOException ioe) {
+ // Will fail since no username and password is provided.
+ }
+ if (count > 1) {
+ throw new RuntimeException("Authenticator called twice");
+ }
+ }
+
/**
* Creates and starts an HTTP or proxy server that requires
* Negotiate authentication.