Contents


Use a Liberty JAAS login module for Bluemix single sign-on

A powerful combination to lighten your login and authentication load

Comments

The Java Authentication and Authorization Services (JAAS) framework provides an API for user authentication and authorization. JAAS decouples application code from user authentication and authorization mechanisms, so you can easily configure different login modules without changing your application code.

Authentication with a traditional Liberty web app

With a traditional Liberty web app, authentication involves gathering user credentials, checking a Subject in the cache, calling the JAAS service to perform authentication against a configured registry, and creating a Subject if absent in the cache. This process also creates an HTTP Session object to identify a user across multiple requests.

Authentication with a Bluemix Liberty web app

A web app that runs on the IBM Bluemix cloud platform with the Liberty for Java runtime uses the Single Sign On (SSO) service for authentication and authorization, and does not have access to user credentials because the OAuth2 protocol handles authentication. For Bluemix Liberty web authentication, the Liberty server is not aware and thus does not generate any Subject and HTTP Session objects to identity the user. In this case, the app must take responsibility to identify the user, and the code must be aware of the underlying login mechanism because the HTTP Servlet APIs (Session and Principal) are not created. A Bluemix service performs SSO with Bluemix User Account and Authentication Service (UAA), which requires an OAuth clientID and client-secret inside the Bluemix UAA.

This tutorial shows how to add Liberty-aware authentication to your app. You'll see how a sample app uses the Bluemix SSO service for user login. The Liberty server creates all necessary objects (Subject, Principal, Session) upon successful authentication. The app code is abstracted from the login mechanism and keeps the burden of user identification with Liberty.

How it works

A custom JAAS login module performs authentication using the Bluemix SSO service and creates a Subject with custom credentials (user name, access-token). A Session object is created by the server. The custom credentials and principals are accessible from downstream code using the Subject API. This login module replaces the Liberty default modules of the system.WEB_INBOUND configuration. The solution assumes:

  • A Bluemix SSO service instance is added to the Liberty application.
  • SSO client configuration data (such as clientID and clientSecret) are available.

The JAAS login module is configured with SSO client configuration data. A Liberty application communicates with this login module and the SSO service using two REST APIs (/dashboard and /sso_dashboard).

The following steps correspond to the numbers in the figure above:

  1. A user invokes the /dashboard API to initiate the login process.
  2. The API initiates an OAuth flow with the SSO service by invoking the /authorize end-point URL with client ID, state (to prevent cross-site-request-forgery), and callback REST API.
  3. After a successful OAuth flow...
  4. The callback API (/sso_dashboard) is invoked by the SSO service with a temporary authentication code and state value.
  5. The callback REST API invokes the custom JAAS login module and passes authentication code.
  6. The login module requests an access token.
  7. The login module retrieves an access token from the SSO service.
  8. The login module also retrieves the user name.
  9. The login module creates a Subject with a Principal and credentials.
  10. The /sso_dashboard API (or any downstream code) can access the information from the Subject API or the Servlet API (HttpServlet.getUserPrincipal()). This way, the Liberty server is aware of this login process and identifies an authenticated user from the Subject cache.

What you'll need to build your application

Run the appGet the code

About the sample application

The sample Liberty app LoginTestApp (click the Get the code button above) invokes the custom login module (CustomLoginModule), which performs JAAS authentication with the Bluemix SSO service. After successful login, the app code can invoke any authentication related API, such as HttpServletRequest.getUserPrincipal(), without knowing the underlying authentication mechanism.

When you run the sample app, an HTML page appears with a link to JAAS login with Bluemix SSO. Click the link to invoke the app's /dashboard REST API and initiate the authentication process. You then see the Bluemix SSO login page where you enter Bluemix credentials. Upon successful authentication, your Bluemix ID and Liberty session ID are displayed.

Step 1. Create a simple Bluemix Liberty JAX-RS application

Write a small Liberty JAX-RS application with two REST APIs (/cloudLogin/dashboard and /cloudLogin/sso_redirect) as shown in the code example below. The /dashboard API, which is the starting point, initiates the authenticate process (lines 44-45). This API invokes the SSO service authorization API with an OAuth client ID, an arbitrary string (state), and a callback REST API (/sso_redirect).

After a successful OAuth flow, the /sso_redirect API is invoked by the Bluemix SSO service with a temporary authorization code and the arbitrary string (state). This API then invokes the Liberty custom login module and passes the authorization code by calling the HttpServletRequest.login(code, client_secret) method (line 63). The API checks whether the login is successful by calling the getUserPrincipal() method. Upon successful login, information about the user (name) and login (access-token, Session) are accessed from the Subject. Line 85 shows that a Session object is created by the Liberty server to identify a user across multiple requests.

43. @GET
44. @Path("/dashboard")
45. public Response dashboard() throws URISyntaxException {
46. 	System.out.println("Dashboard GET request headers: " + headersAsString(request));
47. 	String stateId = "anyUniqueString";
48. 	URI uri = new URI(AUTH_END_POINT + "?state=" + stateId 
49. 			+ "&scope=profile&response_type=code&client_id=" 
50. 			+ CLIENT_ID + "&redirect_uri=" 
51. 			+ buildRESTApiUrlString(SSO_END_POINT_PATH));
52. 	System.out.println("Dashboard GET redirect URI: " + uri);
53. 	return Response.seeOther(uri).build();
54. }
55. @GET
56. @Produces(MediaType.TEXT_HTML)
57. @Path(SSO_END_POINT)
58. public Response ssoDashboard(@QueryParam("code") String code, @QueryParam("state") String state) 
59. 		throws URISyntaxException, UnsupportedEncodingException, WSSecurityException {
60. 	System.out.println("ssoDashboard GET request headers: " + headersAsString(request));		
61. 	try {
62. 		// pass authorization code as user name to CustomeLoginModule
63. 		request.login(code, code);
64. 	} catch (ServletException e) {
65. 		e.printStackTrace();
66. 	}
67. 		if (request.getUserPrincipal() == null) {
68. 		// user is not logged in.
69. 		return Response.status(Status.UNAUTHORIZED).
70. 				type(MediaType.TEXT_HTML).
71. 				entity("You are not authorized.").build();
72. 	}
73. 
74. 	Subject subject = WSSubject.getCallerSubject();
75. 	Set<Object> credentials = subject.getPublicCredentials();
76. 	String userName = request.getUserPrincipal().getName();
77. 	String accessToken = null;
78. 	for (Object obj : credentials) {
79. 		if (obj instanceof HashMap) {
80. 			HashMap<String, String> cred = (HashMap) obj;
81. 			accessToken = cred.get("access-token");
82. 			// create application session with these information.
83. 		}
84. 	}
85. 	String libertySessionId = request.getSession().getId();
86. 	String msg = "<p>Welcome " + userName 
87. 			+ ". You are successfully authenticated using Bluemix SSO service.</p> " 
88. 			+ "<p> Your Liberty session ID :" + libertySessionId +"</p>";
89. 	return Response.status(Status.OK).type(MediaType.TEXT_HTML).entity(msg).build();

Step 2. Implement the Bluemix SSO login module

The login module requires certain URLs (user profile end-point, token end-point, callback login REST API), an OAuth clientID, and an OAuth client-secret to be passed in as options. These values are stored in class variables.

44. public void initialize(Subject subject, CallbackHandler callbackHandler, 
       Map sharedState, Map options) {
45. 		this.subject = subject;
46. 		this.callbackHandler = callbackHandler;
47. 		this.sharedState = sharedState;
48. 
49. 		if (options.containsKey("PROFILE_END_POINT")) {
50. 			profileEndPoint = (String) options.get("PROFILE_END_POINT");
51. 		}
52. 
53. 		if (options.containsKey("CLIENT_SECRET")) {
54. 			clientSecret = (String) options.get("CLIENT_SECRET");
55. 		}
56. 
57. 		if (options.containsKey("SSO_END_POINT_PATH")) {
58. 			ssoEndPointPath = (String) options.get("SSO_END_POINT_PATH");
59. 		}
60. 
61. 		if (options.containsKey("CLIENT_ID")) {
62. 			clientId = (String) options.get("CLIENT_ID");
63. 		}
64. 		
65. 		if (options.containsKey("TOKEN_END_POINT")) {
66. 			tokenEndPoint = (String) options.get("TOKEN_END_POINT");
67. 		}
68. 	}

The login method communicates with the Bluemix SSO service and performs authentication. First, the temporary authentication code is collected using the NameCallback object, as in lines 76-77.

71. public boolean login() throws LoginException {
72. 		
73. 	System.out.println("Login module invoked");
74. 
75. 	// authorization code is passed as user name param value.  
76. 	NameCallback nameCallback = new NameCallback("User:");	
77. 	Callback callbacks[] = new Callback[1];
78. 	callbacks[0] = nameCallback;	
79. 
80. 	try {
81. 		callbackHandler.handle(callbacks);
82. 	} catch (IOException e1) {
83. 		e1.printStackTrace();
84. 	} catch (UnsupportedCallbackException e1) {
85. 		e1.printStackTrace();
86. 	}
87. 
88. 	// get the authorization code
89. 	String code = nameCallback.getName();
92.    CloudAccessToken accessToken = 
93. 		getAccessToken(clientId,
94. 				clientSecret,
95. 				tokenEndPoint,
96. 				code,
97. 				ssoEndPointPath);
98. 		
99. 	// get user name
100. 	HashMap<String, String> headerList = new HashMap<String, String>();
101. 	headerList.put("Authorization", accessToken.getFullAccessToken());
102. 	ASMRestClient restClient = new ASMRestClient();
103. 	ASMRestResponse response = restClient.doGet(profileEndPoint, headerList, null);
104. 	if (response.getStatusCode() != Status.OK.getStatusCode()) {
105. 		System.out.println("User profile end point returned status code: " 
106. 				+ response.getStatusCode());
107. 		return false;
108. 	}
109. 			
110. 	CloudUserInfo userInfo = response.getEntity(CloudUserInfo.class);
111. 	String name = userInfo.getUserDisplayName()[0];
112. 		
113. 	// add access token and user info as JAAS subject credentials.
114. 	HashMap<String, String> map = new HashMap<String, String>();		
115. 	map.put("access-token", accessToken.getAccessToken());
116. 	map.put("access-token-type", accessToken.getTokenType());		
117. 			
118. 	subject.getPublicCredentials().add(map);
119. 		
120. 	java.util.Hashtable<String, Object> customProperties = 
121. 			(java.util.Hashtable<String, Object>) sharedState.
122. 			get(AttributeNameConstants.WSCREDENTIAL_PROPERTIES_KEY);
123. 	customProperties = new java.util.Hashtable<String, Object>();
124. 		
125. 	customProperties.put(AttributeNameConstants.WSCREDENTIAL_UNIQUEID, name);
126. 	customProperties.put(AttributeNameConstants.WSCREDENTIAL_SECURITYNAME, name);
127. 	sharedState.put(AttributeNameConstants.WSCREDENTIAL_PROPERTIES_KEY,
128. 			customProperties);

A JAAS Subject with Principal and credential are created, as shown above:

  • Lines 92-97: An accessToken is retrieved via an HTTP request to the SSO service token end-point with clientID, clientSecret, authorization code, and callback login REST API URL.
  • Lines 102-111: This accessToken is used as an authorization header for the profile end-point request to retrieve the user name.
  • Lines 114-128: The JAAS Principal and credentials are constructed using authenticated user information retrieved from the Bluemix SSO service.
  • Lines 114-118: A public credential is added to the Subject, which contains the access token and its type.
  • Lines 125-128: A hash-table with user information is added to the shared-state variable (passed in the initialize method) to create the JAAS Principal.
  • Line 127: The Liberty framework searches the value of the hash-table object by using a defined key within the shared-state variable and creates Principal.

Step 3. Add the Bluemix SSO service and generate the SSO client configuration

It's time to push the sample app to Bluemix and bind the SSO service to it. Follow the steps in Getting started with Single Sign On to generate the SSO client configuration and provide the /sso_redirect URL as the callback URI value.

Record the clientID and client-secret value, as they are needed in the next step.

Step 4. Configure the custom JAAS login module

The custom login module is configured in the sample Liberty app. A snippet of the server.xml file, which loads the custom login module, is shown below. The library of this login module resides in the server's lib folder and is referenced by the customLoginLib element (line 25). The default login modules of the sytem.WEB_INBOUND configuration are replaced with the custom login and a hashtable module, as in line 30. Lines 33-40 show how to load the custom login module implementation and the options that it expects (cliendID, client-secret, user profile URL, callback REST API URL, and token end-point URL).

24. 	
25. 	<library id="customLoginLib">
26. 		<fileset dir="${server.config.dir}/lib" includes="*.jar"/>
27. 	</library>
28. 	
29. 	
30.   	<jaasLoginContextEntry id="system.WEB_INBOUND" loginModuleRef="custom, hashtable" 
31. 	name="system.WEB_INBOUND"/>
32. 	
33. 	<jaasLoginModule className="login.module.CloudLoginModule" controlFlag="REQUIRED" 
34. 		id="custom" libraryRef="customLoginLib">
35.   		<options CLIENT_ID="${client.id}"
36.   			 CLIENT_SECRET="${client.secret}"
37.   			 PROFILE_END_POINT="${profile.end.point}" 
38.   			 SSO_END_POINT_PATH="${sso.end.point}"
39.   			 TOKEN_END_POINT="${token.end.point}"/>
40.   	</jaasLoginModule>

Step 5. Build and push the application to Bluemix

To build and run the sample app:

  1. Create a Bluemix Liberty app:
    1. Log in to Bluemix and create a Liberty app.
    2. Add a Bluemix SSO service into your space using the cf command:
      cf create-service SingleSignOn standard sso_service
    3. Bind this service to your app using the cf command:
      >cf bind-service app_name sso_service
    4. Generate the SSO client configuration:
      1. Provide the Redirect URI value as:
        https:<hostname>.mybluemix.net:443/api/cloudlogin/sso_redirect
      2. Record the clientId and client-secret.
  2. Fork this project into a new JazzHub project.
  3. Download the sample app:
    1. Set up your Eclipse IDE to download this sample app from JazzHub (see Setting up local Eclipse clients to work with Jazz source control).
    2. Import the sample project into your Eclipse IDE.
    3. Download the Liberty profile runtime (see Download just the Liberty profile runtime).
    4. Download the cf command line program to push your application to Bluemix (see Command line interface).
    5. Download and configure the Eclipse plugin for the Liberty profile (see Download Liberty profile in Eclipse).
    6. Create a Liberty server to deploy your app from the Eclipse server view (for example, Liberty\usr\servers\loginapp).
  4. Build your application:
    1. Specify the Liberty runtime location and Liberty server location in the following ant build scripts:
      • CustomLoginModule\build.xml
      • LoginTestApp\build.xml
          <property name="Liberty.location" value="C:\Liberty"/>
          <property name="server.name" value="loginapp"/>
    2. Specify OAuth clientId and client-secret, which are recorded from the SSO client configuration step, in the LoginTestApp\Config.xml file.
      <server>
        <variable name='client.id' value='YOUR_CLIENT_ID'/>
        <variable name='client.secret' value='YOUR_CLIENT_SECRET'/>
    3. To build the project, perform an ant build of LoginTestApp/build.xml. You'll notice that your server folder (for example, Liberty\usr\servers\loginapp) is updated with dependencies and configurations.
  5. Deploy your app to Bluemix:
    1. Update the manifest.yml file with the app name, host-name, and path to Liberty server (for example, C:\Liberty\usr\servers\loginapp).
    2. Push your app to Bluemix using the cf push command:
      C:\Program Files (x86)\CloudFoundry>cf push -f c:\manifest.yml

After the deployment finishes, you can access your app (https://<hostname>.mybluemix.net/).

Step 6. Run and test the sample application

To test this sample app:

  1. Navigate to your app URL (https://<hostname>.mybluemix.net/). A page with a web link "JAAS login with Bluemix SSO service" appears.
  2. A login from your identity provider (such as IBM or Facebook) will appear to provide your credentials.
  3. A secure access code will be emailed by the SSO service.
  4. Provide the access code and, after successful login with Bluemix SSO, you will see a message similar to:
    You are successfully authenticated using Bluemix SSO, Session ID :g-IZDSgUdVZXfH8E3NAf4fw

Conclusion

In this tutorial, we showed how a Bluemix Liberty app can add single sign-on capability by using JAAS-based authentication with the Bluemix SSO service. You can build on the configurations in the sample program to explore even more benefits.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Security, Cloud computing, Java development
ArticleID=1001314
ArticleTitle=Use a Liberty JAAS login module for Bluemix single sign-on
publish-date=03242015