6829283: HTTP/Negotiate: Autheticator triggered again when user cancels the first one
authorweijun
Thu, 18 Mar 2010 18:26:37 +0800
changeset 5154 07af3c279166
parent 5153 8b10c5dccd73
child 5155 d70c73fdc68e
6829283: HTTP/Negotiate: Autheticator triggered again when user cancels the first one Reviewed-by: chegar
jdk/src/share/classes/sun/net/www/protocol/http/spnego/NegotiateCallbackHandler.java
jdk/test/sun/security/krb5/auto/HttpNegotiateServer.java
--- 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.