This section shows how to authenticate a user against a remote LMS.
By the end of this section, you should be able to:
- Log into Blackboard Learn as an LMS user.
- Perform SSO as this user from Blackboard Learn into the LMS
- Log out of the LMS and Blackboard Learn
Prerequisites
It is expected that you have already completed the following parts of the Getting Started guide:
- Learning Environment Connector Developer Guide - Getting Started
- You are able to build a Connector implementation
- Learning Environment Connector Developer Guide - Snapshot XML
- You are able to import users from the LMS into Blackboard Learn
Download sample Building Block
Code for the sample below can be downloaded from here. The steps below reference this sample.
Implement AuthenticationProvider
In the Getting Started section, we created a stub AuthenticationProvider class. Now it is time to expand on this implementation a bit.
The most important methods for user-based operations are login(), getCookies(), and logout(). Our sample below focuses on these methods. The Javadoc for AuthenticationProvider provides more information about each of these methods.
Sample implementation
public class SampleAuthenticationProvider extends AbstractAuthenticationProvider implements AuthenticationProvider { @Override public boolean login( Id userId, String password ) throws PasswordChangeRequiredException, PasswordChangeForcedException { String lmsUserName = getIntegrationContext().getUserIntegration( userId ).getLmsUsername(); // Simple hack to allow testing with the demo user if ( "username".equals( lmsUserName ) ) { if ( "password".equals(password)) return true; else if ( "changeRequired".equals(password)) throw new PasswordChangeRequiredException( "Password has expired." ); else if ( "changeForced".equals(password)) throw new PasswordChangeForcedException( "Password must be changed on first login." ); else return false; } // // You will need to provide a real method to contact the LMS here. The following // is an example of how a fictional LMS might perform this action. // // Note: When implementing the real version, security concerns must be taken into account. // // Send a request to the LMS's authenticateUser SDK method, with the username and password // to validate. The LMS will return a string indicating that the password was valid, // was valid and has expired, or was invalid. NameValuePair[] parameters = { new NameValuePair( "username", lmsUserName ), new NameValuePair( "password", password ) }; try { String response = sendSecureRequest( "sdk/authenticateUser", parameters ); if ( "true".equalsIgnoreCase( response ) ) return true; else if ( "PasswordChangeRequired".equalsIgnoreCase( response ) ) throw new PasswordChangeRequiredException( "Password has expired." ); else if ( "PasswordChangeForced".equalsIgnoreCase( response ) ) throw new PasswordChangeForcedException( "Password must be changed on first login." ); else return false; } catch ( IOException e ) { LogServiceFactory.getInstance().logError( "Unexpected exception", e ); return false; } } @Override public void logout( Id userId ) { String lmsUserName = getIntegrationContext().getUserIntegration( userId ).getLmsUsername(); // Simple hack to allow testing with the demo user if ( "username".equals( lmsUserName ) ) { return; } // // You will need to provide a real method to contact the LMS here. The following // is an example of how a fictional LMS might perform this action. // // Note: When implementing the real version, security concerns must be taken into account. // // Send a request to the LMS's logoutUser SDK method, with the username of the user // to log out. In this example, a shared secret is also being sent to prove that // this server is allowed to terminate the user's session. String sharedSecret = SampleSettingsHelper.getAuthSetting( getIntegrationContext(), "sharedSecret" ); NameValuePair[] parameters = { new NameValuePair( "username", lmsUserName ), new NameValuePair( "sharedSecret", sharedSecret ) }; try { sendSecureRequest( "sdk/logoutUser", parameters ); } catch ( IOException e ) { LogServiceFactory.getInstance().logError( "Unexpected exception", e ); } } @Override public Set<Cookie> getCookies( Id userId ) { String lmsUserName = getIntegrationContext().getUserIntegration( userId ).getLmsUsername(); Set<Cookie> cookieList = new HashSet<Cookie>(); // Simple hack to allow testing with the demo user if ( "username".equals( lmsUserName ) ) { return cookieList; } // // You will need to provide a real method to contact the LMS here. The following // is an example of how a fictional LMS might perform this action. // // Note: When implementing the real version, security concerns must be taken into account. // // Send a request to the LMS's getUserCookie SDK method, with the username of the // to get the cookie for, along with a shared secret to prove that this server is // allowed to request the user's cookie. The LMS will return a specially encoded // string which we can transform into a cookie on our end. // // An alternate way to perform this action would be to have the LMS include the // user's cookie in the response headers. This is how the Vista Connector is // implemented. String sharedSecret = SampleSettingsHelper.getAuthSetting( getIntegrationContext(), "sharedSecret" ); String cookieDomain = SampleSettingsHelper.getAuthSetting( getIntegrationContext(), "cookieDomain" ); NameValuePair[] parameters = { new NameValuePair( "username", lmsUserName ), new NameValuePair( "sharedSecret", sharedSecret ) }; try { String cookieString = sendSecureRequest( "sdk/getUserCookie", parameters ); Map<String, String> cookieParts = SampleSettingsHelper.parseSettingsText( cookieString ); Cookie cookie = new Cookie( cookieParts.get( "key" ), cookieParts.get( "value" ) ); cookie.setDomain( cookieDomain ); cookie.setMaxAge( -1 ); cookie.setPath( "/" ); cookieList.add( cookie ); } catch ( IOException e ) { LogServiceFactory.getInstance().logError( "Unexpected exception", e ); } // TODO - cache cookies internally so that we don't have to contact the LMS repeatedly return cookieList; } @Override public void setCookies( Id userId, Set<Cookie> cookies ) { // TODO - cache cookies internally, and update the cache here } @Override public String getSharedCookieDomain( Id userId ) { return SampleSettingsHelper.getAuthSetting( getIntegrationContext(), "cookieDomain" ); } @Override public Set<Id> checkActivity( Set<Id> userIds ) { // TODO - check for activity on remote LMS return null; } @Override public void flushCachedCredentials( Id userId ) { // TODO - cache cookies internally, and flush the cache here } /** * Sends an https request to the remote LMS, and returns the LMS's response as a string. */ private String sendSecureRequest( String targetUrl, NameValuePair[] parameters ) throws IOException { String lmsHostname = SampleSettingsHelper.getAuthSetting( getIntegrationContext(), "lmsHostname" ); PostMethod httpPost = new PostMethod( "https://" + lmsHostname + "/" + targetUrl ); httpPost.setRequestBody( parameters ); try { int statusCode = new HttpClient().executeMethod( httpPost ); if ( statusCode != HttpStatus.SC_OK ) { LogServiceFactory.getInstance().logError( "Request failed: statusCode=" + statusCode ); return null; } return httpPost.getResponseBodyAsString(); } finally { httpPost.releaseConnection(); } } }
Testing the sample
- Create a new integration instance for your LMS server.
- Import an XML snapshot containing one or more test users.
- Navigate to the Blackboard Learn login page.
- Log in as user username with password password.