Many popular sites, such as
The
Wall Street Journal,
require a username and password for access. Some sites, such as
Oracle TechNet, implement this through HTTP authentication. Others,
such as the Java Developer Connection, implement it through cookies
and HTML forms. Java’s URL
class can access
sites that use HTTP authentication, though 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, partially because Java doesn’t really support
cookies and partially because this requires parsing and submitting
HTML forms. 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.
Starting in Java 1.2 (but not available in Java 1.1), 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
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 it out of an encrypted file.
To make the URL
class use your subclass, you
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
for it 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 prompt
is provided by the HTTP
server. It’s typically the name of the realm for which
authentication is required. (Some large web servers such as
metalab.unc.edu 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 may not be 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.requestPassword Authentication( )
throws a SecurityException
.
Your 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, then 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( )
will throw 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( )
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.)
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
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 get method:
public String getUserName( ) public char[] getPassword( )
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 he’s typing 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 zeroes. 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.11 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-11. 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.12 is a revised
SourceViewer
program that can ask the user for a
name and password by using the DialogAuthenticator
class.
Example 7-12. 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 e) { System.err.println(args[0] + " is not a parseable URL"); } catch (IOException e) { System.err.println(e); } // 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
3.139.72.78