7.6 Accessing Password-Protected Sites

     

Many popular sites, such as The Wall Street Journal , require a username and password for access. Some sites, such as the W3C member pages, implement this correctly through HTTP authentication. Others, such as the Java Developer Connection, implement it incorrectly through cookies and HTML forms. Java's URL class can access sites that use HTTP authentication, although you'll of course need to tell it what username and password to use. Java does not provide support for sites that use nonstandard, cookie-based authentication, in part because Java doesn't really support cookies in Java 1.4 and earlier, in part because this requires parsing and submitting HTML forms, and, lastly, because cookies are completely contrary to the architecture of the Web. (Java 1.5 does add some cookie support, which we'll discuss in the next chapter. However, it does not treat authentication cookies differently than any other cookies.) You can provide this support yourself using the URLConnection class to read and write the HTTP headers where cookies are set and returned. However, doing so is decidedly nontrivial and often requires custom code for each site you want to connect to. It's really hard to do short of implementing a complete web browser with full HTML forms and cookie support. Accessing sites protected by standard, HTTP authentication is much easier.

7.6.1 The Authenticator Class

The java.net package includes an Authenticator class you can use to provide a username and password for sites that protect themselves using HTTP authentication:

 public abstract class Authenticator extends Object // Java 1.2 

Since Authenticator is an abstract class, you must subclass it. Different subclasses may retrieve the information in different ways. For example, a character mode program might just ask the user to type the username and password on System.in . A GUI program would likely put up a dialog box like the one shown in Figure 7-4. An automated robot might read the username out of an encrypted file.

Figure 7-4. An authentication dialog
figs/jnp3_0704.gif

To make the URL class use the subclass, install it as the default authenticator by passing it to the static Authenticator.setDefault() method:

 public static void setDefault(Authenticator a) 

For example, if you've written an Authenticator subclass named DialogAuthenticator , you'd install it like this:

 Authenticator.setDefault(new DialogAuthenticator( )); 

You only need to do this once. From this point forward, when the URL class needs a username and password, it will ask the DialogAuthenticator using the static Authenticator.requestPasswordAuthentication() method:

 public static PasswordAuthentication requestPasswordAuthentication(   InetAddress address, int port, String protocol, String prompt, String scheme)    throws SecurityException 

The address argument is the host for which authentication is required. The port argument is the port on that host, and the protocol argument is the application layer protocol by which the site is being accessed. The HTTP server provides the prompt . It's typically the name of the realm for which authentication is required. (Some large web servers such as www. ibiblio .org have multiple realms, each of which requires different usernames and passwords.) The scheme is the authentication scheme being used. (Here the word scheme is not being used as a synonym for protocol . Rather it is an HTTP authentication scheme, typically basic.)

Untrusted applets are not allowed to ask the user for a name and password. Trusted applets can do so, but only if they possess the requestPasswordAuthentication NetPermission . Otherwise, Authenticator.requestPasswordAuthentication( ) throws a SecurityException .

The Authenticator subclass must override the getPasswordAuthentication( ) method. Inside this method, you collect the username and password from the user or some other source and return it as an instance of the java.net.PasswordAuthentication class:

 protected PasswordAuthentication getPasswordAuthentication( ) 

If you don't want to authenticate this request, return null , and Java will tell the server it doesn't know how to authenticate the connection. If you submit an incorrect username or password, Java will call getPasswordAuthentication( ) again to give you another chance to provide the right data. You normally have five tries to get the username and password correct; after that, openStream( ) throws a ProtocolException .

Usernames and passwords are cached within the same virtual machine session. Once you set the correct password for a realm, you shouldn't be asked for it again unless you've explicitly deleted the password by zeroing out the char array that contains it.

You can get more details about the request by invoking any of these methods inherited from the Authenticator superclass:

 protected final InetAddress getRequestingSite( ) protected final int         getRequestingPort( ) protected final String      getRequestingProtocol( ) protected final String      getRequestingPrompt( ) protected final String      getRequestingScheme( ) protected final String      getRequestingHost( )  // Java 1.4 

These methods either return the information as given in the last call to requestPasswordAuthentication( ) or return null if that information is not available. ( getRequestingPort( ) returns -1 if the port isn't available.) The last method, getRequestingHost( ) , is only available in Java 1.4 and later; in earlier releases you can call getRequestingSite( ).getHostName( ) instead.

Java 1.5 adds two more methods to this class:

 protected final String getRequestingURL( )  // Java 1.5 protected Authenticator.RequestorType getRequestorType( ) 

The getRequestingURL( ) method returns the complete URL for which authentication has been requestedan important detail if a site uses different names and passwords for different files. The getRequestorType( ) method returns one of the two named constants Authenticator.RequestorType.PROXY or Authenticator.RequestorType.SERVER to indicate whether the server or the proxy server is requesting the authentication.

7.6.2 The PasswordAuthentication Class

PasswordAuthentication is a very simple final class that supports two read-only properties: username and password. The username is a String . The password is a char array so that the password can be erased when it's no longer needed. A String would have to wait to be garbage collected before it could be erased, and even then it might still exist somewhere in memory on the local system, possibly even on disk if the block of memory that contained it had been swapped out to virtual memory at one point. Both username and password are set in the constructor:

 public PasswordAuthentication(String userName, char[] password) 

Each is accessed via a getter method:

 public String getUserName( ) public char[] getPassword( ) 

7.6.3 The JPasswordField Class

One useful tool for asking users for their passwords in a more or less secure fashion is the JPasswordField component from Swing:

 public class JPasswordField extends JTextField 

This lightweight component behaves almost exactly like a text field. However, anything the user types into it is echoed as an asterisk. This way, the password is safe from anyone looking over the user's shoulder at what's being typed on the screen.

JPasswordField also stores the passwords as a char array so that when you're done with the password you can overwrite it with zeros. It provides the getPassword( ) method to return this:

 public char[] getPassword( ) 

Otherwise, you mostly use the methods it inherits from the JTextField superclass. Example 7-13 demonstrates a Swing-based Authenticator subclass that brings up a dialog to ask the user for his username and password. Most of this code handles the GUI. A JPasswordField collects the password and a simple JTextField retrieves the username. Figure 7-4 showed the rather simple dialog box this produces.

Example 7-13. A GUI authenticator
 package com.macfaq.net; import java.net.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class DialogAuthenticator extends Authenticator {   private JDialog passwordDialog;     private JLabel mainLabel     = new JLabel("Please enter username and password: ");   private JLabel userLabel = new JLabel("Username: ");   private JLabel passwordLabel = new JLabel("Password: ");   private JTextField usernameField = new JTextField(20);   private JPasswordField passwordField = new JPasswordField(20);   private JButton okButton = new JButton("OK");   private JButton cancelButton = new JButton("Cancel");      public DialogAuthenticator( ) {     this("", new JFrame( ));   }        public DialogAuthenticator(String username) {     this(username, new JFrame( ));   }        public DialogAuthenticator(JFrame parent) {     this("", parent);   }        public DialogAuthenticator(String username, JFrame parent) {          this.passwordDialog = new JDialog(parent, true);       Container pane = passwordDialog.getContentPane( );     pane.setLayout(new GridLayout(4, 1));     pane.add(mainLabel);     JPanel p2 = new JPanel( );     p2.add(userLabel);     p2.add(usernameField);     usernameField.setText(username);     pane.add(p2);     JPanel p3 = new JPanel( );     p3.add(passwordLabel);     p3.add(passwordField);     pane.add(p3);     JPanel p4 = new JPanel( );     p4.add(okButton);     p4.add(cancelButton);     pane.add(p4);        passwordDialog.pack( );          ActionListener al = new OKResponse( );     okButton.addActionListener(al);     usernameField.addActionListener(al);     passwordField.addActionListener(al);     cancelButton.addActionListener(new CancelResponse( ));        }      private void show( ) {          String prompt = this.getRequestingPrompt( );     if (prompt == null) {       String site     = this.getRequestingSite( ).getHostName( );       String protocol = this.getRequestingProtocol( );       int    port     = this.getRequestingPort( );       if (site != null & protocol != null) {         prompt = protocol + "://" + site;         if (port > 0) prompt += ":" + port;       }       else {         prompt = "";        }            }     mainLabel.setText("Please enter username and password for "      + prompt + ": ");     passwordDialog.pack( );     passwordDialog.show( );        }      PasswordAuthentication response = null;   class OKResponse implements ActionListener {        public void actionPerformed(ActionEvent e) {              passwordDialog.hide( );       // The password is returned as an array of        // chars for security reasons.       char[] password = passwordField.getPassword( );       String username = usernameField.getText( );       // Erase the password in case this is used again.       passwordField.setText("");       response = new PasswordAuthentication(username, password);            }         }   class CancelResponse implements ActionListener {        public void actionPerformed(ActionEvent e) {              passwordDialog.hide( );       // Erase the password in case this is used again.       passwordField.setText("");       response = null;            }         }   public PasswordAuthentication getPasswordAuthentication( ) {          this.show( );         return this.response;        } } 

Example 7-14 is a revised SourceViewer program that asks the user for a name and password using the DialogAuthenticator class.

Example 7-14. A program to download password-protected web pages
 import java.net.*; import java.io.*; import com.macfaq.net.DialogAuthenticator; public class SecureSourceViewer {   public static void main (String args[]) {     Authenticator.setDefault(new DialogAuthenticator( ));     for (int i = 0; i < args.length; i++) {              try {         //Open the URL for reading         URL u = new URL(args[i]);         InputStream in = u.openStream( );         // buffer the input to increase performance          in = new BufferedInputStream(in);                // chain the InputStream to a Reader         Reader r = new InputStreamReader(in);         int c;         while ((c = r.read( )) != -1) {           System.out.print((char) c);         }        }       catch (MalformedURLException ex) {         System.err.println(args[0] + " is not a parseable URL");       }       catch (IOException ex) {         System.err.println(ex);       }              // print a blank line to separate pages       System.out.println( );            } //  end for        // Since we used the AWT, we have to explicitly exit.     System.exit(0);   } // end main }  // end SecureSourceViewer 



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net