Home → 2004/06/06, 11h32

Atom API client example with WSSE authentication

Here is sample code in Java that shows how to access an AtomAPI enabled host to obtain a list of weblogs. This source code is dependance-free: no need to download any non-standard HTTP client library such as Apache's commons-httpclient.

As of today, it works perfectly on both Typepad (see their docs), and Blogger.


import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Sample application that Lists the User's Weblogs from an Atom API enabled
 * host. The code that deals with WSSE was inspired by the Sandler
 * project.
 */
public class GetBlogs {

  public static void main(String[] args) {
    try {
      if (args.length < 3) {
        System.out
            .println("Usage: java GetBlogs <url> <username> <password>");
        System.out.println("<url> - the URL of the AtomAPI-enabled host");
        System.out.println("<username> - the username on the host");
        System.out.println("<password> - the username's password on the host");
        System.out.println();
        System.exit(1);
      }

      URL url = new URL(args[0]);
      String username = args[1];
      String password = args[2];

      URLConnection connection = url.openConnection();
      connection
          .addRequestProperty("X-WSSE", getWSSEHeader(username, password));

      InputStream in = null;
      try {
        in = connection.getInputStream();
      } catch(Exception e){
        e.printStackTrace();
        in = ((HttpURLConnection)connection).getErrorStream();
      }
      BufferedReader res = new BufferedReader(new InputStreamReader(in, "UTF-8"));
      String inputLine;
      while ((inputLine = res.readLine()) != null)
        System.out.println(inputLine);
      res.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

 private static String getWSSEHeader(String username, String password)
     throws Exception {

   byte[] nonceB = generateNonce();
   String nonce = base64Encode(nonceB);

   String created = generateTimestamp();

   String password64 = getBase64Digest(nonceB, created.getBytes("UTF-8"),
       password.getBytes("UTF-8"));
   StringBuffer header = new StringBuffer("UsernameToken Username=\"");
   header.append(username);
   header.append("\", ");
   header.append("PasswordDigest=\"");
   header.append(password64);
   header.append("\", ");
   header.append("Nonce=\"");
   header.append(nonce);
   header.append("\", ");
   header.append("Created=\"");
   header.append(created);
   header.append("\"");
   return header.toString();
 }

 private static byte[] generateNonce() {
     String nonce = Long.toString(new Date().getTime());
     return nonce.getBytes();
 }

 private static String generateTimestamp() {
   SimpleDateFormat dateFormatter = new SimpleDateFormat(
       "yyyy-MM-dd'T'HH:mm:ss'Z'");
   return dateFormatter.format(new Date());
 }

 private static synchronized String getBase64Digest(byte[] nonce,
     byte[] created, byte[] password) {
   try {
     MessageDigest messageDigester = MessageDigest.getInstance("SHA-1");
     // SHA-1 ( nonce + created + password )
     messageDigester.reset();
     messageDigester.update(nonce);
     messageDigester.update(created);
     messageDigester.update(password);
     return base64Encode(messageDigester.digest());
   } catch (java.security.NoSuchAlgorithmException e) {
     throw new RuntimeException(e);
   }
 }

 private static synchronized String getBase64Digest(byte[] password) {
   try {
     MessageDigest messageDigester = MessageDigest.getInstance("SHA-1");
     messageDigester.reset();
     messageDigester.update(password);
     return base64Encode(messageDigester.digest());
   } catch (java.security.NoSuchAlgorithmException e) {
     throw new RuntimeException(e);
   }
 }

 private static String base64Encode(byte[] bytes) {
   // Use Sun's encoder in this sample.
   return new sun.misc.BASE64Encoder().encode(bytes);
 }
}

Comments (18)

  1. You can get rid of this comment now:

    As of today, it works perfectly on Typepad (see their docs), but on Blogger, it fails apparently randomly.

    ;-)

    On 2004/06/06 at 22h34, from David Stewart

  2. Done. The only trace left about this is in you comment ;)

    On 2004/06/06 at 22h45, from Claude

  3. Glad Sandler inspired you and I appreciate the plug

    On 2004/06/07 at 15h23, from Mark Lussier

  4. Hi, I am trying this code against Blogger, and I get:

    Apache Tomcat/4.1.24 - Error report
    HTTP Status 401 - type Status reportmessage description This request requires HTTP authentication ().Apache Tomcat/4.1.24

    Could you shed some light into this? Thanks so much!

    On 2004/06/29 at 08h29, from Yong Woo

  5. My guess is that you are using the right URL to Blogger's Atom host but the userid or password is/are invalid. You need to specify a userid that exists on Blogger with the corresponding password.

    On 2004/06/29 at 08h48, from Claude

  6. That was embarassing. I had a typo in my password. Thank you so much for your help.

    On 2004/06/29 at 08h58, from Yong Woo

  7. I have another question for you if you got a moment. Have you tried POSTing using OutputStream and BufferedWriter?

    I encounter a "Malformed XML. Mode indicated was 'xml' but data was not xml", which is strange because I am sending a validating Atom XML.

    Thanks in advance!

    On 2004/06/29 at 11h57, from Yong Woo

  8. I forgot to escape and set type to title.

    Hey, thanks so much for this post and your help, you really helped me understand Atom much better.

    On 2004/06/29 at 13h26, from Yong Woo

  9. Glad it could help.

    I had the same problem in my post sample, sample that I could have added here too, but I decided to leave it as an exercise to the reader ;)

    On 2004/06/29 at 13h30, from Claude

  10. This. Totally. Rocks.

    What's the license on this? Is it opensource? BSD? Email me at bradneuberg_NOSPAM@yahoo.comNOSPAM

    On 2004/07/23 at 04h23, from Brad Neuberg

  11. No problems about using it. This is just sample code, not a library.

    On 2004/07/23 at 20h49, from Claude

  12. I was just playing about with a MovableType installation and it kept refusing to authenticate because the UsernameToken was timing out. To fix this, I just had to offset my local date for UTC.

    Here is the verbose version of the code:

    Date now = new Date();
    TimeZone zone = TimeZone.getDefault();
    long offset = zone.getOffset(now.getTime());

    SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    String offsetDateString = dateFormatter.format(new Date(System.currentTimeMillis() - offset));

    On 2004/08/20 at 18h08, from Trevor F. Smith

  13. Also, for other MT quirks check out Ben Hammersley's handy page on the MT Atom API, including why you can't just use your MT password to authenticate:

    http://www.benhammersley.com/undocumented_features/atom_api_in_movable_type.html

    On 2004/08/20 at 18h10, from Trevor F. Smith

  14. Thank you for these tips Trevor. I will update the code. And nice app you built BTW -- http://transmutable.com/93PhotoStreet/

    On 2004/08/21 at 09h20, from Claude

  15. No problem, and thanks for the plug.

    There still seems to be a problem with client machines with clocks set very differently than MT servers, but TypePad doesn't seem to check this.

    On 2004/09/02 at 20h21, from Trevor F. Smith

  16. I've just checked in a handy Java library to manipulate TypePad and MT servers via Atom: http://sourceforge.net/projects/javaatomiclibs/

    MT3.11 fixed issues with the AuthenticationToken timeouts, but introduced a problem during entry deletion. *sigh*

    On 2004/09/11 at 11h00, from Trevor F. Smith

  17. re: atom lib on SourceForge

    Simple and seems to do the work. Good work Trevor.

    On 2004/09/15 at 22h56, from Claude

  18. What URL does everyone use for Blogger?

    On 2004/09/30 at 21h48, from