Tuesday Jun 30, 2009

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 
106 

The 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.

 

Friday Jun 12, 2009

OpenSSO and the Quest for the Holy Grails

grailquest

With apologies to Monty Python Fans....

Did you ever want to use OpenSSO with Grails? Now you can!

The astute reader of this blog (that's you Mom!) will know that you can protect Spring applications with the OpenSSO Spring 2 Security provider

As it turns out, Grails has a plugin that let's you use Spring Security for authentication and authorization. If we marry this plugin with the provider, we ought to be able to use OpenSSO with Grails. Don't ya love it when a plan comes together?

To get started, you will need to download the OpenSSO Grails plugin from here, and install it using the grails install-plugin command. 

To give credit where credit is due, the OpenSSO plugin is largely based on the work of the Spring Security Grails plugin. The original authors are T.Yamamoto, Hatian Sun, and Burt Beckwith. My humble contribution is the OpenSSO glue code and the integration of the OpenSSO Spring Security extension. For reasons that I won't go into here, I elected to create a separate version of the plugin specifically for OpenSSO (ping me if you want the details).

I would also suggest downloading this sample Grails application that demonstrates how to use the plugin in a Grails Application.

 Yes. But what does it do?

 The OpenSSO plugin provides the following functionality to a Grails Application:

Single Sign On (SSO) with OpenSSO:

The plugin delegates logon to OpenSSO. If the user has previously authenticated to OpenSSO, the browser will present a cookie containing the SSO token. Providing the session is still valid, the user will be transparently signed on the Grails application.  If the user does not have a token, the plugin redirects them to OpenSSO. After succesfull authentication, the user is then redirected back to the application.  

This means that your application is not responsible for authentication. In fact, there are no logon or password screens to maintain as OpenSSO handles it for you. One of the nice benefits of this approach is that the authentication method and strength is factored out of the application. Want to use one time passwords? How about AD?  No problem - just configure the authentication chain in OpenSSO. No changes to your application.  

URL Policy Enforcement

 The plugin provides for enforcement of URL policy using OpenSSO.  This works quite nicely with Grails and it's "REST" like structure for controller URLs. So we can (for example), allow one group of users to /list controller items, and another group of users to /update or /create new items. Custom controller methods (beyond the standard CRUD methods) can use the same mechanism. 

Note that this largely eliminates the need for the @Secured annotations in Grails code - since the same effect can be implemented using URL policy. This externalizes the authorization into OpenSSO - which is generally a good thing. 

Controller Security Methods  

 The plugin injects several security methods into your controllers to provide access to the security context. Here is sampling of methods available to controllers:

isUserInRole("ROLE_MANAGER")   - true if a user belongs to the specified Role
isUserLogon()	 -  true if the user is logged on (authenticated)
getGrantedAuthorities() - returns an array of Strings representing the Granted Authorities (Spring terminology for role names) that this user posseses.

GSP Tags

The plugin provides access to GSP security tags. These tags can be used in your Grails view to drive the display based on the user's role or authentication status.

For example:

<g:ifNotGranted role="ROLE_MANAGERS">You are not a Manager!!! </g:ifNotGranted>

<g:ifAllGranted role="ROLE_MANAGERS">Congrats. You are a manager!</g:ifAllGranted>
Time permitting, I may create a cookbook on how to get this all working with the sample application. Drop me a line if you are interested.

PS. Just what is the wing speed velocity of swallow?  

Monday Feb 18, 2008

Getting Groovy with OpenSSO REST Services



Recent builds of OpenSSO now include WS-\* and REST identity services. Check out Marina Sum's blog for an excellent overview.


Now it turns out that Groovy is a great way to explore REST services. Here is an example using OpenSSO (if you are trying this out at home change your base URL to reflect your OpenSSO installation).



def baseurl = "http://localhost:28080/opensso/identity/"

// Create and execute REST query string
def query = { String action, Map args ->
def s = baseurl + action;
def sep = "?"
args.each { key,value ->
def encoded = java.net.URLEncoder.encode(value,"UTF-8")
s += "${sep}${key}=${encoded}"
sep = "&"
}
return s.toURL().text
}

// authenticate to OpenSSO
def authenticate = { username, password ->
String s = query("authenticate", [ username:username, password:password] )
int i = s.indexOf('=')
if( i >= 0 )
return s.substring(i+1).trim()
else
return s;
}

// fetch the users attributes. Must supply the token returned by authenticate
def getAttributes = { token -> query("attributes", [subjectid:token]) }

// Test to see if the user identified by token is allowed to access the URI
def authorize = { token, uri, action = "GET" -> query("authorize", [subjectid:token, uri:uri, action:action]) }

def token = authenticate("user1", "password")

println getAttributes(token)
println authorize(token, "http://localhost:8080/protected")





As Mr. Powers would say, Groovy Baby"
About

Things that amuse me

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
News

No bookmarks in folder

Blogroll