Java Tip 47: URL authentication revisited

You're not yet using Java 1.2? Access password-protected URLs with Java 1.1

In the

last tip

, we introduced the following scenario: You're using your favorite browser to surf the 'Net, and you encounter a URL that requires authentication from a proxy or HTTP server. What you see on the screen is the standard box in which you must enter your username and password so as to gain access to the site. To refresh your memory, here's what that screen looks like:

From a Java program, you'll find yourself in trouble: When you try to read from an

InputStream

associated with the URL, a

FileNotFoundException

is thrown. Starting with Java 1.2, you can take advantage of the

Authenticator

class, which, as mentioned above, you learned about in Java Tip 46. However, with versions prior to Java 1.2, you need to know if the URL you want to access is password-protected before you try to read from it. If the URL is protected, you have to manually configure the username-password response for each URL, so you can properly access the contents. The response is in the form of what's called an authorization request property. The property gets associated with the

URLConnection

for the URL. Once the authorization setting is installed, when you get the URL's contents, the information will be accessible. The authorization string is of the form "

Authorization

: Basic username:password," where the basic authentication fields are encoded in Base64. (A description of the encoding scheme is found in RFC 1113. An RFC is a "request for comments" document that, basically, describes some kind of Internet standard. In this case, #1113 describes Message Encipherment and Authentication Procedures. See

Resources

below.) To do the encoding, you can use the

BASE64Encoder

class in the

sun.misc

package, or you can create your own class. To set up this access, you simply have to configure the

URL

's

URLConnection

to have the proper "Authorization" request property. The following steps describe everything:

Step 1: Create URL

 URL url = new URL (urlString);

Step 2: Get username and password for specificURL

Step 3: Place in String separated by colon (":")

 String userPassword = theUsername + ":" + thePassword;

Step 4: Encode the bytes of the string

 String encoding = new sun.misc.BASE64Encoder().encode (userPassword.getBytes());

Step 5: Create a URLConnection from the URL

 URLConnection uc = url.openConnection();

Step 6: Set the "Authorization" request property for the URLConnection

 uc.setRequestProperty ("Authorization", "Basic " + encoding);

And now, the solution

Accessing a password-protected URL in Java 1.1 is demonstrated in its entirety below, with its own authentication prompt dialog. The code is as similar as can be to the Java 1.2

Authenticator

example provided in Java Tip 46. If you choose to avoid

sun.misc

classes, or if this happens to be unavailable on your platform, the source for a converter is included below.

 import java.awt.*;
 import java.awt.event.*;
 import java.io.*;
 import java.net.*;
 
public class Auth11 extends Frame implements ActionListener {
 
  private TextField tf = new TextField();
   private TextArea  ta = new TextArea();
 
  public void actionPerformed (ActionEvent e) {
     String s = tf.getText();
     if (s.length() != 0)
       ta.setText (fetchURL (s));
   }
 
  public Auth11() {
 
    super ("URL11 Password");
 
    // Setup screen
     add (tf, BorderLayout.NORTH);
     ta.setEditable(false);
     add (ta, BorderLayout.CENTER);
     tf.addActionListener (this);
     addWindowListener (new WindowAdapter() {
       public void windowClosing (WindowEvent e) {
         dispose();
         System.exit(0);
       }
     });
   }
 
  private String fetchURL (String urlString) {
     StringWriter sw = new StringWriter();
     PrintWriter  pw = new PrintWriter(sw);
 
    try {
       URL url = new URL (urlString);
 
     // Popup Window to request username/password password
       MyAuthenticator ma = new MyAuthenticator();
       String userPassword = ma.getPasswordAuthentication(this, url.getHost());
 
      // Encode String
       String encoding = new sun.misc.BASE64Encoder().encode (userPassword.getBytes());
 
      // or
       // String encoding = Base64Converter.encode (userPassword.getBytes());
 
      // Need to work with URLConnection to set request property
       URLConnection uc = url.openConnection();
       uc.setRequestProperty  ("Authorization", "Basic " + encoding);
       InputStream content = (InputStream)uc.getInputStream();
       BufferedReader in   = 
         new BufferedReader (new InputStreamReader (content));
       String line;
       while ((line = in.readLine()) != null) {
         pw.println (line);
       }
     } catch (MalformedURLException e) {
       pw.println ("Invalid URL");
     } catch (IOException e) {
       pw.println ("Error reading URL");
     }
     return sw.toString();
   }
 
 
  public static void main (String args[]) {
     Frame f = new Auth11();
     f.setSize(300, 300);
     f.setVisible (true);
   }
 
  class MyAuthenticator {
     String getPasswordAuthentication(Frame f, String prompt) {
       final Dialog jd = new Dialog (f, "Enter password", true);
       jd.setLayout (new GridLayout (0, 1));
       Label jl = new Label (prompt);
       jd.add (jl);
       TextField username = new TextField();
       username.setBackground (Color.lightGray);
       jd.add (username);
       TextField password = new TextField();
       password.setEchoChar ('*');
       password.setBackground (Color.lightGray);
       jd.add (password);
       Button jb = new Button ("OK");
       jd.add (jb);
       jb.addActionListener (new ActionListener() {
         public void actionPerformed (ActionEvent e) {
           jd.dispose();
         }
       });
       jd.pack();
       jd.setVisible(true);
       return username.getText() + ":" + password.getText();
     }
   }
 
}
      /*********************************************************************
      * BASE 64 encoding of a String or an array of bytes.
      *
      * See also RFC 1421.
      * 
      * @author
      *    Unknown
      *  @author
      *    David W. Croft
      * @version
      *    1998-06-08
      *********************************************************************/
 
     public class  Base64Converter
      //////////////////////////////////////////////////////////////////////
      //////////////////////////////////////////////////////////////////////
      {
 
     public static final char [ ]  alphabet = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',   //  0 to  7
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',   //  8 to 15
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',   // 16 to 23
        'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',   // 24 to 31
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',   // 32 to 39
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v',   // 40 to 47
        'w', 'x', 'y', 'z', '0', '1', '2', '3',   // 48 to 55
        '4', '5', '6', '7', '8', '9', '+', '/' }; // 56 to 63
 
     //////////////////////////////////////////////////////////////////////
      //////////////////////////////////////////////////////////////////////
 
     public static String  encode ( String  s )
      //////////////////////////////////////////////////////////////////////
      {
        return encode ( s.getBytes ( ) );
      }
 
     public static String  encode ( byte [ ]  octetString )
      //////////////////////////////////////////////////////////////////////
      {
        int  bits24;
        int  bits6;
 
       char [ ]  out
          = new char [ ( ( octetString.length - 1 ) / 3 + 1 ) * 4 ];
 
       int outIndex = 0;
        int i        = 0;
 
       while ( ( i + 3 ) <= octetString.length )
        {
          // store the octets
          bits24  = ( octetString [ i++ ] & 0xFF ) << 16; 
          bits24 |= ( octetString [ i++ ] & 0xFF ) <<  8; 
          bits24 |= ( octetString [ i++ ] & 0xFF ) <<  0;
 
         bits6 = ( bits24 & 0x00FC0000 ) >> 18; 
          out [ outIndex++ ] = alphabet [ bits6 ];
          bits6 = ( bits24 & 0x0003F000 ) >> 12; 
          out [ outIndex++ ] = alphabet [ bits6 ];
          bits6 = ( bits24 & 0x00000FC0 ) >> 6; 
          out [ outIndex++ ] = alphabet [ bits6 ];
          bits6 = ( bits24 & 0x0000003F );
          out [ outIndex++ ] = alphabet [ bits6 ]; 
        }
 
       if ( octetString.length - i == 2 )
        {
          // store the octets 
          bits24  = ( octetString [ i     ] & 0xFF ) << 16; 
          bits24 |= ( octetString [ i + 1 ] & 0xFF ) <<  8;
 
         bits6 = ( bits24 & 0x00FC0000 ) >> 18;
          out [ outIndex++ ] = alphabet [ bits6 ]; 
          bits6 = ( bits24 & 0x0003F000 ) >> 12; 
          out [ outIndex++ ] = alphabet [ bits6 ]; 
          bits6 = ( bits24 & 0x00000FC0 ) >> 6; 
          out [ outIndex++ ] = alphabet [ bits6 ];
 
         // padding
          out [ outIndex++ ] = '='; 
        }
        else if ( octetString.length - i == 1 )
        {
          // store the octets 
          bits24 = ( octetString [ i ] & 0xFF ) << 16;
 
         bits6 = ( bits24 & 0x00FC0000 ) >> 18;
          out [ outIndex++ ] = alphabet [ bits6 ];
          bits6 = ( bits24 & 0x0003F000 ) >> 12; 
          out [ outIndex++ ] = alphabet [ bits6 ];
 
         // padding
          out [ outIndex++ ] = '='; 
          out [ outIndex++ ] = '='; 
        }
 
       return new String ( out );
      }
 
     //////////////////////////////////////////////////////////////////////
      //////////////////////////////////////////////////////////////////////
      }

Remember that using this program will require you to find a URL with a username and password that you know. If you happen to have acquired a free account from

Discover Brokerage Direct

for the

Authenticator

tip, you can use it again here. Otherwise, you can get a free password at the

registration desk

. Then, as with the last tip, you can try the program with a URL of

http://www.lombard.com/cgi-bin/Quotes/quote

.

John Zukowski is a software mage with MageLang Institute, author of Java AWT Reference from O'Reilly & Associates and Borland's JBuilder: No experience required from Sybex, as well as the Focus on Java guide at the Mining Company.

Learn more about this topic

  • RFC 1113 (describes encoding format) http://andrew2.andrew.cmu.edu/rfc/rfc1113.html
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more