HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

Single sign-on is becoming an important issue for corporations and Java developers. Corporations require applications to be secure. Users demand applications to be easy to use. Usually, the more secure an application is, the more difficult it is to use. For example, users are inundated with remembering multiple logins to different systems.

Part 1 of this article (JDJ, Vol. 6, issue 11) provided readers with a conceptual understanding of single sign-on (SSO). In Part 2 we explain the programming and setup issues related to SSO for Web and standalone applications. With this knowledge, you'll be able to implement SSO solutions for your applications.

Balancing Security with Easy Usage
It was noted that more than 45% of calls to corporate help desks involve user ID or password-related issues. To avoid memorizing multiple logins users put their passwords in insecure locations (like their monitors).

SSO addresses these issues by creating a single-user ID and password that can access multiple applications.

Implementing SSO for Web Applications
Single sign-on for Web applications is a protocol between the client (browser), member server, and login server. The SSO protocol establishes a set of procedures for authentication. All of the SSO procedures utilize encryption to guarantee the confidentiality of data, redirection, and JavaScript to facilitate client communication between the member servers and login server, and cookies to store SSO information. To understand the SSO protocol, we first focus on SSO's components. The first component is encryption.

Encryption
Encryption is used to encrypt URL parameter and cookie values. Sample JCE code is available on the JDJ Web site (www.sys-con.com/java/sourcec.cfm). Using JCE encryption is straightforward, but make sure you convert raw ciphertext to hex before putting it into the cookie payload or setting it as a request parameter value. The URLEncoder an URLDecoder have problems encoding and decoding ciphertext. By converting the ciphertext to hex, you don't need to URLEncode data.

To facilitate hex encoding and pluggable encryption algorithms, you'll want to create a general encryption and decryption interface, then an abstract class that implements the interface with concrete facade subclasses that utilize different encryption algorithms. This allows you to provide the encryption and decryption services with the flexibility to plug in new algorithms in the future (see Figure 1 for UML's advantage).

Figure 1
Figure  1:

Now that we've covered encryption, let's talk about redirection.

A redirect is a type of command sent from the server that sends a browser to another location. It sends users to different servers without the user's intervention. There are two ways of redirecting users in Java. The first is through the HttpServletResponse object's sendRedirect("fullURL"). The second sends HTML and JavaScript to the client, which initiates a POST.

Here is an example of using the HttpServletResponse object:

public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{

res.sendRedirect ("https://loginserver.yourcorp.com/loginservlet? name=val");

...

This example tells the client browser to make a GET request to https://loginserver.yourcorp.com/loginservlet?name=val. Because the request is a GET, the user will be able to see the full URL, including the query string in the location bar of the browser. In addition, the amount of information that can be passed using a query string is limited and varies for different Web and app servers. Finally, it's also easier to initiate a replay attack by copying the URL string and pasting it into another browser on a different machine.

A better way of redirecting is by using HTML and JavaScript (see Listing 1).

HTML and JavaScript redirects have strong advantages when compared to response.sendRedirect( ). The first advantage is that the HTML form action is a POST. This means the query string information is not visible in the browser location bar. The second is there's no restriction on the amount of information that can be passed to the alternate server. The only drawback is JavaScript has to be enabled on the browser.

If you don't like the idea of shifting the user from one site to the next, JavaScript or hidden frames can be used to communicate with multiple member servers from a single logical page without physically redirecting the browser.

Listing 2 provides a JavaScript example where a page from the login server tells the member servers to set up a session with the browser receiving the page.

The one drawback of using <SCRIPT> tags is that processing is synchronous. The browser has to wait for partner1 to authenticate the browser before attempting partner2. Hidden frames allow you to work asynchronously, although it will only work with a frames-capable browser. See Listing 3 for an example using hidden frames.

The only frame viewable by the user is "viewable". Listing 4 shows what TempFrame.jsp would look like.

In the preceding example, frames "setPartner1cookie" and "setPartner2cookie" log the client browser into other sites asynchronously. Another advantage of using JavaScript or hidden frames is the ability to receive cookies from multiple sites from a single page.

Now that you understand redirection and its alternatives, let's look at cookies.

The Role of Cookies
Most cookies are made up of six parts: name, value, domain, path, expiration date, and secure flag. The name uniquely identifies a single cookie and is used when retrieving, deleting, and updating cookies. The value stores the payload (text) of the cookie. A cookie can hold up to 4K of information. The domain restricts which machine or DNS domains can read an individual cookie. To share cookies between "members.yourdomain.com" and "stocks.yourdomain.com", set your cookies domain to ".yourdomain.com". If you set the cookies domain to "members.yourdomain.com", then the only computer that can read the cookie is "members.yourdomain.com". The path determines which directory can read the cookie and specifies the directory in which files can manipulate a particular cookie.

If you set the path to "/", then any page on the server can read the cookie. The expiration date determines when a cookie is invalidated. The Cookie class has a method, setMaxAge (), which sets how long a cookie should last in seconds. Finally, the secure flag on a cookie ensures that the cookie can be sent only to the client or server over SSL. It doesn't encrypt the information in the cookie file on the client.

Here are several important cookie guidelines.

1.   Set the maxAge to -1 to create in-memory cookies.

2.    Set the domain to ".yourDomain.com". This allows other machines in your domain to share cookie information directly.

3.   Set the path to "/". This allows any of the JSP and servlets on a machine to obtain the cookie, regardless of their location on the machine.

4.   Use the Properties object to pass multiple name value pairs within the value of the cookie or request parameter.

5.   The secure flag should be set when the login server sets cookies. See Listing 5 for sample code.

To change the value of a cookie or delete a cookie, you'll get the cookie, change an attribute, and send it back using response.addCookie (Cookie changedCookie ). Debugging cookies can be problematic. To minimize debugging time during development, do the following:

1.   Set the browser to notify you every time a cookie is set. Go to Tools / Internet Options /Security and select Prompt in both cookies settings. When you get the Security Alert prompt in the browser, you can see all six attributes of a cookie by clicking the "More Info" button.

2.   Create a cookie display JSP to print out the cookies and their attributes.

3.   Create a cookie delete JSP to delete all of the SSO cookies. We've covered all the components of a Web SSO solution. The last step is the SSO protocol.

SSO Protocol
The SSO protocol describes how browser clients interact with member servers and the login server to enable SSO. The protocol has many variations and you can tailor the protocol presented to suit your needs. The SSO protocol differs slightly, depending on whether all machines participating in SSO reside within the same DNS domain. When machines are under a common domain, cookies set by one machine can be read by another. The LCMServlet on the member server (MS) and login server (LS) serve as the central point of contact for authentication requests and replies. All communication to LCM servlets on MS and LS use HTTPS.

When going from member server to login server, you'll usually have to send the following:

1.   Action: This tells the login server what the member server wants, e.g., authenticate, register, etc.

2.   Return URL: This is the original URL to a protected resource.

3.   Extra information for associated action: This might be a custom login page for the authenticate action.

4.   Server identifier: This identifies which server is making the request.

When going from LS to MS, you'll send some or all of the following:

1.   User information: This will typically be the user ID and identifying information.

2.   Action: Authenticate, register, logout.

3.   Result code for initiating action: Failed, successful, etc.

4.   Return URL: The return URL represents the original URL that the user was trying to obtain.

5.   Timestamp: This is added security to protect against replay attacks. Only requests that have a timestamp less than five- minutes-old will be accepted. Using timestamps requires a clock synchronization service for all servers participating in SSO.

Login Use Case
Remember there are many ways to implement a single sign-on protocol. The actual protocol you'll use depends on your requirements and environment. Figure 2 shows a login use case for servers that share the same DNS domain.

Figure 2
Figure  2:

Servers with the Same Domain
1.   The user hasn't authenticated and makes a request for a protected resource.

2.   The protected resource notices that the user isn't authenticated and redirects the user to the LCMMSServlet passing the requested URL, the server ID, and an action of authenticate.

3.   The LCMMSServlet looks for a shared authorization cookie that would have been set by the login server and doesn't find one.

4.   The LCMMSServlet takes all the information passed to it and redirects the user to the login server for authentication.

5.   The LCMLSServlet receives the request, notices that the user hasn't logged in, and presents a customized login page for the member server that initiated the request.

6.   Once the user has successfully authenticated, the login server sets two cookies. The first cookie is an authentication cookie, that stores the user's encrypted authentication information. The second cookie stores the servers that the client has visited (for logout purposes). The login server redirects the user back to the member server's LCMMSServlet with encrypted information in a cookie.

7.   The LCMMSServlet decrypts the user information and logs the user in.

8.   Once authenticated, the LCMMSServlet forwards the request to a protected resource.

9.   If the user tries to access a protected resource on another server in the same domain...

10.   The request is redirected to the LCMMSServlet using HTTPS.

11.   The LCMMSServlet checks the authenticated cookie and notices that the user has already logged in. The LCMMSServlet sets up a session and writes its server information to the "servers" shared cookie.

12.   The LCMMSServlet then forwards the request to a protected resource.

Things will be slightly different if the participating servers reside in different domains. See Figure 3 for an example.

Figure 3
Figure  3:

Different Domains
1.   The user has not authenticated and makes a request for a protected resource.

2.   The protected resource notices that the user is not authenticated and redirects the user to the login server passing the requested URL, the server ID, and an action of authenticate.

3.   The login server receives the request, notices that the user hasn't logged in, and presents a customized login page for the member server that initiated the request.

4.   Once the user has successfully authenticated, the login server sets two cookies. The first cookie is an authentication cookie, which stores the user's encrypted authentication information. The second cookie stores the servers that the client has visited (for logout purposes). The login server redirects the user back to the member server's LCMMSServlet with encrypted information in a request parameter.

5.   The LCMMSServlet decrypts the user information and logs the user in.

6.   Once authenticated the LCMMSServlet forwards the request to a protected resource.

7.   If the user tries to access a protected resource on another server in the same domain...

8.   The request is redirected to the login server using HTTPS.

9.   The login server checks the "authorization" cookie and notices that the user has already logged in. The login server adds the new server to the "servers" cookies.

10.   Then login server redirects the user back to the member server's LCMMSServlet with encrypted information in a request parameter.

11.   The LCMMSServlet decrypts the user information and logs the user in.

12.   The LCMMSServlet then forwards the request to a protected resource.

Things are similar in both cases, except the protected resources now redirect to the login server instead of to their local LCMMSServlet. With shared cookies, the member server LCMMSServlets can read the authentication information directly out of the cookie set by the login server. With unshared cookies, the login server needs to tell each member server of a user's status directly (through request parameters) versus indirectly (through a shared cookie).

If all this redirection is making you dizzy, there's a trick you can use to access multiple sites from a single Web page. Let's look at how it works in the logout use case (see Figure 4).

Figure 4
Figure  4:

Steps for Multiple Sites
1.   The user clicks a logout icon that is linked to the LCMLSServlet of the login server.

2.   The login server sends back a temporary page with <SCRIPT> tags or hidden frames.

3.   Each of the <SCRIPT> tags of hidden frames contacts a different member server to logout and delete cookies all at once.

4.   Once all of the member servers have been contacted, the temporary page is posted back to the login server with the member server logout responses. The login server then sends the logout response page.

The beauty of using hidden frames or a JavaScript SRC attribute is that you go to the login server only once and can access all of the member servers without redirects.

You should have a solid understanding of single sign-on for Web applications. Now we'll discuss single sign-on for standalone applications.

SSO for Standalone Applications
Single sign-on for standalone applications is supported through Kerberos, JAAS, and Sun utility classes. Kerberos provides the single sign-on infrastructure and authentication service. JAAS authenticates users through Kerberos and restricts users based on permissions in a JAAS-specific policy file.

Sun JAAS utility classes (Login and MyAction) help you SSO-enable any Java application. Part 1 discussed Kerberos and JAAS in sufficient detail, so let's look at the Sun utility classes. The Login class encapsulates all of the JAAS-specific code to load the login modules, prompt the user for credentials if necessary, and authenticate the user. The MyAction run( ) method calls the main method of the application that you're SSO-enabling.

To apply what we have learned, let's SSO-enable a Java client application that makes HTTP requests to a Web server.

How to SSO-Enable Java
Let's look at HttpSocketClient.java (see Listing 6). All the client does is open a socket to a Web server HTTP port and read the requested file.

Here are the steps to SSO-enable your Java applications:
1.   Get Kerberos set up and running.

2.   Use the Login class to serve as an entry point for your application and to initiate the JAAS protocol. The Login class does the following:

  • Receives the main( ) class of your application as arguments and any arguments your application needs.
  • Uses the class name of your top-level application class or the -D property java.security.auth.login.config to look up your login configuration file. After finding the LoginContext config file, the Login class will look for the section that has the same name as your top-level application class.
  • Authenticates the user.
  • Associates a subject (with a principal representing the user) with the current access control context.
  • Creates an instance of the MyAction class (also in Login.java) and executes its run method. The MyAction run method uses reflection to load your application's main ( ) class and invokes its public static main method, passing it the application arguments.

    3.   Create a separate JAR for your application classes. In our case, we have HttpSocketClient.jar containing HttpSocketClient.java.

    4.   Configure a separate JAAS policy file for your application. Look at the HttpSocketClient.policy file (see Listing 7). Notice that we are using Login.jar to carry out the JAAS protocol and run our application. Login.jar contains both the Login and MyAction classes. We physically separated Sun's utility classes from our application classes so the JAAS classes could run unrestricted. The codebase assumes Login.jar and HttpSocketClient.jar are in the current directory from where you are running the program.

    The only client that can run HttpSocketClient is "your_kerb_username". The first permission allows the authenticated user's program to make a socket connection to any host. The second permission allows the client to use the ticket-granting service to get a ticket-granting ticket (TGT). The ticket-granting ticket allows clients to access kerbertized applications without logging in multiple times. The third permission allows the client to initiate a secure context with the specified server_service_principal.

    5.   Configure the LoginContext configuration file (as specified through the -Djava.security.auth.login.config) to point to Krb5LoginModule. Here is the LoginContext configuration file (sharedLogin.conf ).

    HttpSocketClient { com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true; };

    The sharedLogin.conf is used to specify the different authentication mechanisms JAAS will use. Here we use the Krb5LoginModule. The Krb5LoginModule interfaces with the Kerberos software client. The "required" flag indicates that this module has to commit successfully for the user to be authenticated. The useTicketCache option tells the LoginModule to look for the ticket-granting ticket in the Kerberos client cache.

    If the TGT doesn't exist, JAAS will prompt users for their user IDs and passwords and log them into Kerberos (which generates a TGT). If the TGT exists, the Krb5LoginModule retrieves the principal associated with the TGT. JAAS then checks the Kerberos principal with the principal specified in the JAAS policy file (HttpSocketClient.policy). If the principals match, the application is run using the permissions in the JAAS policy file (HttpSocketClient.policy). If they don't match, the user isn't allowed to run the JAAS-protected application.

    6.   Run your application through the Login class. To start the application execute the following command on a single line:

    java -classpath Login.jar;HttpSocketClient.jar
    -Djava.security.manager
    -Djava.security.krb5.realm=<your_realm>
    -Djava.security.krb5.kdc=<your_kdc>
    -Djava.security.policy=HttpSocketClient.policy
    -Djava.security.auth.login.config=sharedLogin.conf
    Login HttpSocketClient <host> <port_number> <path>
    If you haven't logged into Kerberos with kinit, you will be prompted for your Kerberos credentials. The Login utility class will authenticate you against Kerberos and set up your Kerberos client with a ticket-granting ticket. If the client logged into Kerberos using kinit or Leash32, the Krb5LoginModule will look for his or her TGT in the user cache. The TGT will live on your machine for a default of 600 minutes, so remember to delete your TGT using kdestroy or Leash32 before logging out of your computer.

    You now have an understanding of single sign-on. You've also seen the issues related to single sign-on - as well as possible solutions. With this practical knowledge in hand, you can now build your own single sign-on solution.

    Author Bios
    Donald Levy is the director of alumni relations and development information systems at Stanford University. Donald has a BS from MIT and an MS from Columbia University. donald.levy@stanford.edu

    Abraham Kang is an integration architect for Infogain's Enterprise Integration Solutions group. His focus is J2EE, security, and enterprise application integration. Abraham has been developing in Java for five years. abraham_kang@yahoo.com

    	
    
    
    
    Listing 1
    
       <html>
       <head>
       </head>
       <body onLoad="document.myForm.submit()">
       <form action="https://loginserver.yourcorp.com/webapp/login
       servlet" name="myForm" method="POST">
       <input type="hidden" name="key" value="!@#$EncryptedString!@#$">
       </form>
       </body>
       </html>
    
    
    Listing 2
    
    <html>
    <head>
    <title>Hello World
    <SCRIPT language="JavaScript" SRC="https://partner1/servlet/LCMMSServlet/login?data=ALKSDFJQWER...JLQKWE">
    </SCRIPT>
    <SCRIPT language="JavaScript"
    SRC="https://partner2/servlet/LCMMSServlet/login?data=ALKSDFJQWER...JLQKWE">
    </SCRIPT>
    <SCRIPT language="JavaScript">
    function postForm() {
       document.myForm.submit( );
    }
    </SCRIPT>
    </head>
    
    <body bgcolor=#FFFFFF onLoad="postForm()">
    <form action="https://myserver/servlet/LCMMSServlet/authenticated" 
    method="POST" name="myForm">
    <input type="hidden" name="data" value="ALKSDFJQWER...JLQKWE">
    <input type="hidden" name="url" value="/requested/url?param1=val1¶m2=val2">
    </form>
    </body>
    </html>  
    
    
    
    Listing 3
    
    <FRAMESET ROWS="100%,0%,0%" onLoad="submitViewableFrameForm()">
        <FRAME NAME="viewable" SRC="TempFrame.jsp">
        <!--The frame below logs in the browser to partner1 -->
        <FRAME NAME="setPartner1cookie"
     
    SRC="https://partner1:7002/servlet/LCMMSServlet/login?data=ALKSDFJQWER...JLQKWE"> 
    
    
        <!--The frame below logs in the browser to partner2 -->
        <FRAME NAME="setPartner2cookie"
     
    SRC="https://partner2:7002/servlet/LCMMSServlet/login?data=ALKSDFJQWER...JLQKWE">
    </FRAMESET>
    
    
    
    Listing 4
    
    <html>
    <head>
    </head>
    <body bgcolor=#FFFFFF>
    Put some text here like "Logging in...Please wait."
    <form action="https://myserver/servlet/LCMMSServlet/authenticated"
                    method="POST" name="myForm" target="_top">
    <input type="hidden" name="data" value="ALKSDFJQWER...JLQKWE">
    <input type="hidden" name="url" value="/requested/url?param1=val1¶m2=val2">
    </form>
    </body>
    </html>
    
    
    
    Listing 5
    
    CryptTool ct = CryptToolFactory.getCryptTool( ... );
    Properties p = new Properties ( );
    ... //Get user Id
    String userId = ...;
    p.setProperty ("uid",userId);
    p.setProperty("anotherProp", someValue);
    
    
    //The String returned is a hex encoded ciphertext
    String encryptedInfo = ct.encrypt(p);
    Cookie c = new Cookie ("SSO",encryptedInfo);
    c.setMaxAge(-1);
    c.setDomain(".yourDomain.com");
    c.setPath("/");
    
    
    //If this is a login server cookie and cookie has to be sent over SSL
    c.setSecure(true);
    
    
    //Send cookie to client
    response.addCookie (c );
    
    
    
    Listing 6
    
    package jdj.sso.test;
    
    
    import java.net.*;
    import java.io.*;
    
    
    public class HttPSocketClient {
    
    
       public static void main(String[] args) throws Exception {
            String host = null;
            int port = -1;
            String path = null;
            for (int i = 0; i < args.length; i++)
                System.out.println(args[i]);
    
    
            if (args.length < 3) {
                System.out.println(
                    "USAGE: java HttPSocketClient " +
                    "host port requestedfilepath");
                System.exit(-1);
            }
            try {
                host = args[0];
                port = Integer.parseInt(args[1]);
                path = args[2];
            } catch (IllegalArgumentException e) {
                 System.out.println("USAGE: java HttPSocketClient " +
                     "host port requestedfilepath");
                 System.exit(-1);
            }
    
    
            try {
    
    
    
                Socket socket = new Socket(host,port);
    
    
                PrintWriter out = new PrintWriter(
                                      new BufferedWriter(
                                      new OutputStreamWriter(
                                      socket.getOutputStream())));
    
    
                out.println("GET " + path + " HTTP/1.1");
                out.println();
                out.flush();
    
    
                BufferedReader in = new BufferedReader(
                                        new InputStreamReader(
                                        socket.getInputStream()));
    
    
                String inputLine;
    
    
                while ((inputLine = in.readLine()) != null)
                    System.out.println(inputLine);
    
    
                in.close();
                out.close();
                socket.close();
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
       }
    }
    
    
    
    Listing 7
    
    grant CodeBase "file:./Login.jar" {
             permission java.security.AllPermission;
    };
    
    
    grant CodeBase "file:./HttpSocketClient.jar",
            Principal javax.security.auth.kerberos.KerberosPrincipal
                    "your_kerb_username@your_realm" {
    
    
            permission java.net.SocketPermission "*", "connect";
    
    
            permission javax.security.auth.kerberos.ServicePermission
                    "krbtgt/your_realm@your_realm",
                    "initiate";
    
    
            permission javax.security.auth.kerberos.ServicePermission
    
    
    "server_service_principal@your_realm",
                    "initiate";
    };
    
      
     
    

    Download Assoicated Source File (~ 24.0 KB)

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: info@sys-con.com

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.