Authenticating a JavaFX application using OpenSSO
At JavaONE 2009, Super Pat showed a nice JavaFX demo that used OpenSSO identity services to authenticate and authorize a user. The demo displays a username / password dialog box to collect the user's credentials, which are sent to OpenSSO using identity services. If the user provides the correct credentials, a token is returned that represents their OpenSSO session. This token can be sent back in subsequent identity services calls to perform authorization checks.
I wanted to know if we could use the browser's SSO token in preference to displaying the dialog box in JavaFX. This approach has a couple of advantages:
- Authentication is abstracted out from our application - and is handled by OpenSSO where it belongs. This makes it easy to modify the authentication chain. For example, we can use two factor authentication - without modifying our JavaFX code.
- We can share the SSO session across both browser and JavaFX applications.
The key to making this work is the Java Plugin, which provides a bridge between a Java applet (or JavaFX) and the browser.
Here is a small JavaFX example that uses the plugin to gain access to the SSO token. The example works as follows:
- The JavaFX applet grabs a reference to the browsers DOM using the plugin.
- The DOM is queried for the OpenSSO cookie (iPlanetDirectoryPro).
- If the cookie is found, the token value is sent to OpenSSO identity services to validate the session.
- If the cookie is not found, or the OpenSSO session is not valid, the applet opens a browser window to the OpenSSO login page. After successful authentication the user will now have a valid token in their session.
Here is the JavaFX utility class that handles the REST identity calls to OpenSSO. We use the HttpRequest class to do the heavy lifting:
| /Users/warrenstrange/src/tmp/JavaFXOpenSSOTest/src/openssotest/Util.fx |
1 /* 2 * Util.fx 3 * 4 * Created on Jun 18, 2009, 5:09:55 PM 5 */ 6 7 package openssotest; 8 9 import javafx.io.http.HttpRequest; 10 import javafx.io.http.HttpHeader; 11 12 13 /** 14 * Utility functions to support OpenSSO integration 15 * 16 * @author warrenstrange 17 */ 18 19 20 // todo: We should get this from opensso 21 def cookieName = "iPlanetDirectoryPro"; 22 23 24 /** 25 * Parse the OpenSSO token from the cookie string 26 */ 27 28 public function getTokenFromString(cookies:String) { 29 var token = null; 30 31 if( cookies != null ) { 32 var start = cookies.indexOf(cookieName); 33 if( start > 0 ) { 34 var ts = start + cookieName.length()+1; 35 var te = cookies.indexOf(";", ts); 36 if( te < 0) { 37 token = cookies.substring(ts); 38 } 39 else 40 token = cookies.substring(ts,te); 41 } 42 } 43 return token; 44 } 45 46 /** 47 * Authenticate using OpenSSO identity services (/identity) 48 * We pass in the SSO token we get from the browser. 49 * 50 * Note this is an async process. We start the request and return it to the caller. 51 * When the request is complete the setAuth callback function will be 52 * called. This function will be passed in a boolean indicating if the 53 * user has been authenticated or not. 54 */ 55 56 public function authenticate(token: String, setAuth: function(isAuth:Boolean):Void): HttpRequest { 57 58 var request: HttpRequest = HttpRequest { 59 location: "http://opensso.my2do.com:8080/opensso/identity/isTokenValid" 60 method: HttpRequest.POST 61 62 headers: [ 63 HttpHeader { 64 name: HttpHeader.CONTENT_TYPE; 65 value: "application/x-www-form-urlencoded"; 66 }, 67 HttpHeader { 68 name: HttpHeader.CONTENT_LENGTH; 69 value: "0"; 70 } 71 72 HttpHeader { 73 name: "Cookie"; 74 value: "{cookieName}={token}"; 75 } 76 ]; 77 78 onInput: function(is: java.io.InputStream) { 79 // grab response from opensso 80 try { 81 var conv = new StreamConverter(is); 82 var s = conv.convertStreamToString(); 83 println("Response back from opensso={s}"); 84 if( s != null and s.indexOf("true") > 0) { 85 setAuth(true); 86 } 87 else 88 setAuth(false); 89 } finally { 90 is.close(); 91 } 92 } 93 94 onException: function(ex: java.lang.Exception) { 95 println("onException - exception: {ex.getClass()} {ex.getMessage()}"); 96 } 97 }; 98 99 request.start(); 100 101 return request; 102 103 } 104 105 106The Main.fx panel shows the SSO Token (if present), and the users authentication status.
Apologies for the lame GUI!
| /Users/warrenstrange/src/tmp/JavaFXOpenSSOTest/src/openssotest/Main.fx |
1 /* 2 * Main.fx 3 * 4 * Created on Jun 16, 2009, 11:45:58 AM 5 */ 6 7 package openssotest; 8 9 import javafx.stage.Stage; 10 import javafx.scene.Scene; 11 import javafx.scene.text.Text; 12 13 import com.sun.javafx.runtime.adapter.Applet; 14 15 import javafx.geometry.HPos; 16 import javafx.scene.layout.Flow; 17 import javafx.scene.control.Button; 18 import javafx.io.http.HttpRequest; 19 20 21 import java.net.URL; 22 23 24 25 /** 26 * Sample JavaFX application that authenticate the user using OpenSSO. 27 * The user's browswer is quried for the SSO token. If it is not found a new 28 * browwer window is opened up to the OpenSSO login page. 29 * 30 * @author warrenstrange 31 */ 32 33 var applet: Applet = FX.getArgument("javafx.applet") as Applet; 34 var window = netscape.javascript.JSObject.getWindow(applet); 35 var document : org.w3c.dom.html.HTMLDocument = DOMHelper.getDocument(applet); 36 37 var cookies = document.getCookie(); 38 var token = Util.getTokenFromString(cookies); 39 40 41 var prequest: HttpRequest; // holds state of authenticate async REST call 42 43 def width = 800; 44 def height = 600; 45 46 var authenticated = false; 47 48 49 var url =new URL("http://opensso.my2do.com:8080/opensso"); 50 51 Stage { 52 title: "JavaFX / OpenSSO Test" 53 width: width 54 height: height 55 scene: Scene { 56 content: [ 57 58 Flow { 59 vertical: true 60 height: 300 // columns will wrap at 300 61 hgap: 5 // horizontal gap between columns 62 vgap: 5 // vertical gap between nodes in a column 63 nodeHPos: HPos.LEFT // each node will be left-aligned within its column 64 content: [ 65 66 Text { 67 content: bind if( token != null ) { 68 "SSOToken = {token}" 69 } 70 else "No Token"; 71 72 }, 73 Text { 74 content: bind if (authenticated ) 75 { "Authenticated!" } else "Not Authenticated"; 76 77 }, 78 79 Button { 80 text: "Authenticate Now!"; 81 action: function() { 82 cookies = document.getCookie(); 83 token = Util.getTokenFromString(cookies); 84 println ("Authenticate called with {token}"); 85 86 // call authenticate identity service 87 // when complete, it will call our anon function 88 // if we are NOT authenticated, a new browser window 89 // will be launched pointing the user at the opensso 90 // login page 91 prequest = Util.authenticate(token, 92 function(isAuthenticated:Boolean):Void { 93 authenticated = isAuthenticated; 94 if( not authenticated ) { 95 var c = applet.getAppletContext(); 96 c.showDocument(url, "_blank"); 97 } 98 } ); 99 println ("Authenticate returned"); 100 } 101 } 102 103 ] 104 } 105 ] 106 } 107 } 108
You can download the complete netbeans project here.
