Christian HagerSoftware Engineer // Technical Director // Photographer

Integrating Spring Security and Eclipse RAP (Part 1)

In this blog series I will explain how Spring Security and Eclipse RAP can be convinced to work together. This first post of the series will describe what needs to be done to get Spring-Security’s authentication up and running in an OSGi environment like an Eclipse RAP application. Later parts of the series will focus on:

  • authorization on user interface as well as on service level
  • filtering

So lets get started with authentication. To follow the examples you will need the following prerequisites:

  1. A RAP target
  2. Spring Security 3.0.x
  3. Spring 3.0.x
  4. Spring Dynamic Modules 1.2

As base for the RAP application we will use the application with a view template. Next we will need to configure our project to run with Spring Dynamic Modules. Since Spring DM lies beyond the focus of this article I will assume your are already familiar with the framework. If not a good place to start is the Spring DM manual which can be found here. Next we will create an OSGi service handling the authentication. Therefore I created an api bundle containing the service interface.

package de.nordiccoding.securityservice.api;

public interface SecurityService
{
 public boolean authenticate(
   final String username,
   final String password );
}

Now we’ll create another bundle containing the implementation and configuration for the security service.

package de.nordiccoding.securityservice.impl;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import de.nordiccoding.securityservice.api.SecurityService;

public class SecurityServiceImpl
  implements SecurityService
{
 private AuthenticationManager authenticationManager = null;

 @Override
 public boolean authenticate(
   String username,
   String password )
 {
  Authentication aut = new UsernamePasswordAuthenticationToken( username, password );
  try
  {
   aut = authenticationManager.authenticate( aut );
   SecurityContextHolder.getContext().setAuthentication( aut );
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
  return aut.isAuthenticated();
 }

 public void setAuthenticationManager(
   AuthenticationManager authenticationManager )
 {
  this.authenticationManager = authenticationManager;
 }

 public AuthenticationManager getAuthenticationManager()
 {
  return authenticationManager;
 }
}

Since we now have the implementation class for the security service we can start to create the required spring configuration to get the service up and running. Therefore we add the file META-INF/spring/context.xml containing the following content.

<beans
 schemalocation="http://www.springframework.org/schema/beans                         http://www.springframework.org/schema/beans/spring-beans.xsd                        http://www.springframework.org/schema/security                         http://www.springframework.org/schema/security/spring-security.xsd"
 security="http://www.springframework.org/schema/security" xmlns="http://www.springframework.org/schema/beans"
 xsi="http://www.w3.org/2001/XMLSchema-instance">
 <bean class="de.nordiccoding.securityservice.impl.SecurityServiceImpl"
  id="securityService">
  <property name="authenticationManager" ref="authenticationManager">
  </property>
 </bean>
 <bean class="org.springframework.security.authentication.ProviderManager"
  id="authenticationManager">
  <property name="providers">
   <list>
    <ref bean="authenticationProvider">
     <ref bean="anonymousProvider">
     </ref>
    </ref>
   </list>
  </property>
 </bean>
 <bean
  class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"
  id="authenticationProvider">
  <property name="userDetailsService" ref="userService">
  </property>
 </bean>
 <bean
  class="org.springframework.security.authentication.AnonymousAuthenticationProvider"
  id="anonymousProvider">
  <property name="key" value="SomeUniqueKeyForThisApplication">
  </property>
 </bean>
 <security:user-service id="userService">
  <security:user authorities="ROLE_USER" name="tom"
   password="tom">
  </security:user>
 </security:user-service>
</beans>

So what does this configuration do? First it create an instance of our service bean with a required property “authenticationManager”. So second we create a Spring Security Authenticationmanager. The Authenticationmanager depends on a list of auhtentication providers so we configure one for anonymous access and another one for authenticating our registered users. For simplicity we configure our userservice to be running in memory. In a production environment you would probably authenticate against a database, ldap or some other system.

At this point there is one more thing missing for our service to be available as an OSGi service. To export our service via OSGi we need to add the file META-INF/spring/osgi-context.xml with the following content.

<beans osgi="http://www.springframework.org/schema/osgi"
 schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd                      http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"
 xmlns="http://www.springframework.org/schema/beans" xsi="http://www.w3.org/2001/XMLSchema-instance">
 <osgi:service id="securityServiceOSGi"
  interface="de.nordiccoding.securityservice.api.SecurityService" ref="securityService"></osgi:service>
</beans>

Since our security service is now running we can make use of it in our RAP application. For our users to be able to login we first need some sort of login dialog.

package de.chager.sandbox.securityservice.ui;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

import de.chager.sandbox.securityservice.api.SecurityService;

/**
 * A Login-Dialog for RAP-based Applications
 * 
 * @author Dipl.-Inf. (FH) Christian Hager
 */
public class LoginDialog extends TitleAreaDialog {

 private Text textUser;
 private Text textPassword;
 private Button loginButton;

 /**
  * Create the dialog.
  * 
  * @param parentShell
  */
 public LoginDialog(Shell parentShell) {
  super(parentShell);
  setShellStyle(SWT.NO_TRIM);
 }

 /**
  * Create contents of the dialog.
  * 
  * @param parent
  */
 @Override
 protected Control createDialogArea(Composite parent) {

  // Set the title
  setTitle("OSGi + Spring Security Integration Demo");

  // Set the message
  setMessage("Please enter your login data!",
    IMessageProvider.INFORMATION);

  // Set the image
  setTitleImage(Activator.getImageDescriptor("icon/lock.png")
    .createImage());

  Composite container = (Composite) super.createDialogArea(parent);
  Composite composite = new Composite(container, SWT.NONE);
  composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
  composite.setLayout(new GridLayout(2, false));

  Label labelUser = new Label(composite, SWT.NONE);
  labelUser.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
    false, 1, 1));
  labelUser.setText("User");

  ModifyListener emptyListener = new EmptyListener();

  textUser = new Text(composite, SWT.BORDER);
  textUser.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
    1, 1));
  textUser.setTextLimit(30);
  textUser.addModifyListener(emptyListener);

  Label labelPassword = new Label(composite, SWT.NONE);
  labelPassword.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
    false, 1, 1));
  labelPassword.setText("Password");

  textPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
  textPassword.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
    false, 1, 1));
  textPassword.setTextLimit(30);
  textPassword.addModifyListener(emptyListener);

  return container;
 }

 @Override
 protected void okPressed() {

  int status = authenticate();

  switch (status) {
  case SecurityService.STATUS_LOGIN_BAD_CREDENTIALS:
   setErrorMessage("Login failed due to bad credentials.");
   break;
  case SecurityService.STATUS_LOGIN_FAILED_UNKOWN:
   setErrorMessage("Login failed for an unknown reason. Please conact an administrator!");
   break;
  case SecurityService.STATUS_LOGIN_DISABLED:
   setErrorMessage("Login failed because of disabled account. Please conact an administrator!");
   break;
  default:
   super.okPressed();
   break;
  }
 }

 @Override
 protected void initializeBounds() {
  super.initializeBounds();
  Rectangle displayBounds = Display.getCurrent().getBounds();
  Point size = getShell().getSize();
  int x = (displayBounds.width - size.x) / 2;
  int y = (displayBounds.height - size.y) / 2;
  getShell().setLocation(x, y);
 }

 /**
  * Create contents of the button bar.
  * 
  * @param parent
  */
 @Override
 protected void createButtonsForButtonBar(Composite parent) {
  loginButton = createButton(parent, IDialogConstants.OK_ID, "Login",
    true);
  loginButton.setEnabled(false);
 }

 private int authenticate() {

  int status = SecurityService.STATUS_LOGIN_FAILED_UNKOWN;

  BundleContext bundleContext = Activator.getDefault().getBundle()
    .getBundleContext();
  ServiceReference ref = bundleContext
    .getServiceReference(SecurityService.class.getName());

  try {
   SecurityService securityService = (SecurityService) bundleContext
     .getService(ref);
   status = securityService.authenticate(textUser.getText(),
     textPassword.getText());
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   bundleContext.ungetService(ref);
  }

  return status;
 }

 @Override
 protected Point getInitialSize() {
  return new Point(400, 200);
 }

 class EmptyListener implements ModifyListener {

  @Override
  public void modifyText(ModifyEvent event) {
   setErrorMessage(null);
   if (textUser.getText().length() > 0
     && textPassword.getText().length() > 0) {
    loginButton.setEnabled(true);
   } else {
    loginButton.setEnabled(false);
   }
  }
 }

}

When the user pushes the login button the dialog calls the authenticate method which tries to authenticate the user using our security service. If authentication fails an error message is display in the message area of the dialog.

A good place to open this dialog would be in the createUI method of the RAP application’s “Application” class.

public int createUI() {
 Display display = PlatformUI.createDisplay();
 LoginDialog loginDialog = new LoginDialog(new Shell());
 loginDialog.open();
 WorkbenchAdvisor advisor = new ApplicationWorkbenchAdvisor();
 return PlatformUI.createAndRunWorkbench(display, advisor);
}

If we created everything correct we should now be able to start the application and log in using tom:tom as username:password combination. Any other user should be rejected.

Tags: , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *