Co-operation: Eclipse JAAS + Eclipse RAP

Java Authentication and Authorization Service, kurz JAAS, ist in der Java-Welt das Standardvorgehen zur Authentifizierung und Autorisierung in Java-Anwendungen. Das Eclipse JAAS – noch ein Incubator Projekt – will die Lücke zwischen JAAS Standard und Eclipse PDE schliessen.

Zur Authentifizierung bietet Eclipse JAAS verschiedene Extensionpoints, die es ermöglichen, die Bestandteile von JAAS in der plugin.xml vorzunehmen. Bestandteil dieses Beitrags ist aber zunächst die Authentifizierung in einer Eclipse RAP Anwendung mittels JAAS.

org.eclipse.equinox.security.loginModule

Der loginModule Extensionpoint ermöglicht die Registrierung eines JAAS LoginModule.

<extension id="myLoginModule"
           name="My Login Module"
           point="org.eclipse.equinox.security.loginModule">
   <loginModule
       class="org.foo.bar.MyLoginModule"
       description="My Login Module">
   </loginModule>
</extension>

<extension id="unixLoginModule"
           name="Unix Login Module"
           point="org.eclipse.equinox.security.loginModule">
   <loginModule
       <!-- part of standard jdk -->
       class="com.sun.security.auth.module.UnixLoginModule"
       description="Unix Login Module">
   </loginModule>
</extension>

org.eclipse.equinox.security.callbackHandlerMapping

Mit diesem Extensionpoint wird das Mapping zwischen eine JAAS Loginkonfiguration und einem konkreten CallbackHandler definitiert.

<extension point="org.eclipse.equinox.security.callbackHandlerMapping">
  <callbackHandlerMapping
     callbackHandlerId="org.foo.bar.basicAuthDialog"
     configName="MyLogin">
  </callbackHandlerMapping>
</extension>

org.eclipse.equinox.security.loginConfigurationProvider

<extension id="myLoginConfigurationProvider"
        point="org.eclipse.equinox.security.loginConfigurationProvider">
      <loginConfigurationProvider
          class="org.foo.bar.MyLoginConfigProvider"/>
</extension>

org.eclipse.equinox.security.callbackHandler

Der CallbackHandler ermöglicht Nutzerinteraktionen, zb. zur Eingabe des Logins und Passworts im Dialog oder auf der Kommandozeile.

<extension id="basicAuthDialog"
           name="Basic Authentication Dialog"
           point="org.eclipse.equinox.security.callbackHandler">
   <callbackHandler
      class="org.foo.bar.BasicAuthHandler">
   </callbackHandler>
</extension>

Im Eclipse RAP Projekt können die meisten Ansätze aus RCP übernommen werden. Das CVS (dev.eclipse.org) liefert auch eine RCP-Beispielanwendung mit, die als Basis für das RAP-Projekt dienen kann.

public class BasicAuthHandler extends AbstractLoginDialog {
 
  private String username;
  private char[] password;
 
  protected Control createDialogArea(Composite parent) {
    // implement user interface
  }
 
  public void internalHandle() {
    Callback[] callbacks = getCallbacks();
    for (int i = 0; i &lt; callbacks.length; i++) {
      if (callbacks[i] instanceof NameCallback) {
        ((NameCallback) callbacks[i]).setName(username);
      } else if (callbacks[i] instanceof PasswordCallback) {
        ((PasswordCallback) callbacks[i]).setPassword(password);
      }
    }
  }
}

Die wichtigsten Ausschnitte des abstrakten Logindialogs mit Implementierung des CallbackHandler Interface:

public abstract class AbstractLoginDialog extends TitleAreaDialog
  implements CallbackHandler {
 
  Callback[] callbackArray;
 
  protected final Callback[] getCallbacks() {
    return this.callbackArray;
  }
 
  public abstract void internalHandle();
 
  public void handle(final Callback[] callbacks) throws IOException {
    this.callbackArray = callbacks;
    final Display display = Display.getDefault();
    display.syncExec(new Runnable() {
      public void run() {
        isCancelled = false;
        setBlockOnOpen(false);
        open();
 
        final Button okButton = getButton(IDialogConstants.OK_ID);
        okButton.setText("Login");
        okButton.addSelectionListener(new SelectionListener() {
 
          public void widgetSelected(final SelectionEvent event) {
            processCallbacks = true;
          }
 
          public void widgetDefaultSelected(final SelectionEvent event) {
            // nothing to do
          }
        });
 
        final Button cancel = getButton(IDialogConstants.CANCEL_ID);
        cancel.addSelectionListener(new SelectionListener() {
 
          public void widgetSelected(final SelectionEvent event) {
            isCancelled = true;
            processCallbacks = true;
          }
 
          public void widgetDefaultSelected(final SelectionEvent event) {
            // nothing to do
          }
        });
      }
    });
 
    try {
      ModalContext.setAllowReadAndDispatch(true);
      ModalContext.run(new IRunnableWithProgress() {
 
      public void run(final IProgressMonitor monitor) {
        // Wait here until OK or cancel is pressed, then let it rip.
        // The event listener is responsible for closing the dialog
        // (in the loginSucceeded event).
        while (!processCallbacks) {
          try {
            Thread.sleep(100);
          } catch (final Exception e) {
            // do nothing
          }
        }
        processCallbacks = false;
        // Call the adapter to handle the callbacks
        if (!isCancelled()) {
          internalHandle();
 
        }
      }, true, new NullProgressMonitor(), Display.getDefault());
    } catch (final Exception e) {
      final IOException ioe = new IOException();
      ioe.initCause(e);
      throw ioe;
    }
  }
  ...
}

Beispielanwendung

Eine Beispielanwendung, die vor dem Laden und Starten der Workbench ein Login sicher stellt, könnte zb. so aussehen:

package org.foo.bar;
 
public class Application implements IEntryPoint {
 
  public int createUI() {
    Display display = PlatformUI.createDisplay();
    if (login(display)) {
      WorkbenchAdvisor advisor = new ApplicationWorkbenchAdvisor();
      return PlatformUI.createAndRunWorkbench(display, advisor);
    }
    return 0;
  }
 
  private boolean login(final Display display) {
    String configName = "MyLogin";
    URL configUrl = Activator.getDefault().getBundle()
                     .getEntry("data/jaas_config.txt");
    ILoginContext secureContext = LoginContextFactory
                     .createContext(configName, configUrl);
 
    Subject s = null;
    do {
      try {
        s = secureContext.getSubject();
      } catch (LoginException e) {
        // handle login exception
      }
    } while (s == null);
    return true;
  }
}

Und schliesslich die zugehörige JAAS Loginkonfiguration in data/jaas_config.txt.

MyLogin {
    org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
        extensionId="org.foo.bar.unixLoginModule";

    org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
        extensionId="org.foo.bar.myLoginModule"
};

6 Responses to “Co-operation: Eclipse JAAS + Eclipse RAP”

  1. Mark Miller Says:

    Danke danke danke ….

    this is the first page with a proper implementation of the new equinox/JAAS methodology that i have found.
    Many thanks for the blog post.

    I am trying to adapt it for use with a com.sun.security.auth.module.LdapLoginModule implementation together with embedded Apache DS.

    Mark

  2. Lars Martin Says:

    Hi Mark, thanks for your comment.

    You’re right, Eclipse JAAS is not that widely-used today. The move to the RT repository was a big step forward.

    If you simply want to use JAAS in your RCP application you don’t need to use Eclipse JAAS. But I really like the extension point idea. Additionally Eclipse JAAS provides UI for security settings based on RCP. Maybe one of my next blogs will cover that topic.

    Don’t hesitate to contact me if you have any further questions.

    Lars

  3. Tiago Nunes Says:

    Hello Lars,

    Thank you for this article, I found a lot of value in it. I’m having dependency problems though:

    RAP’s target platform doesn’t include the org.eclipse.equinox.security plugins. I installed them there manually, and it solves a few issues, but now I’m having access restriction problems with several classes that Eclipse is resolving in the javax.security packages. For example, for the Callback class (with import javax.security.auth.callback.*;), I get:

    Access restriction: The type Callback is not accessible due to restriction on required library C:\Program Files\Java\jdk1.6.0_07\jre\lib\rt.jar

    Did you have these problems on set-up? If not, any idea of what might be going wrong? Thank you for any pointers.

    Tiago

  4. Ruediger Herrmann Says:

    Only lately that I stumbled over this blog entry. I think that this information is very useful but rather hard to find as it is written in German.

    Did you think about providing an English translation? If you need a place to put it, what about the RAP FAQ [1], or someplace in Equinox?

    [1] http://wiki.eclipse.org/RAP/FAQ

  5. Laura Bunea Says:

    Hi,

    I implemented a simple RAP application using JAAS based on your blog. It works just fine, but I found an error. On the login page, try to press refresh. The application enters in a deadlock and the following errors is displayed in the log:
    java.lang.IllegalStateException: The context has been disposed.
    You have to restart the server to login into the application. Somehow the context is disposed, but I didn’t call explicitly anywhere in the application display.dispose().

    In your application, did you encountered such a problem?

    Regards,
    Laura

  6. Barnabas Says:

    The same happens when I’m causing a browser refresh (either by F5 or button press).
    I have to re-launch via ‘Launch the RAP application’ in the plugin.xml or the whole JBoss
    when deployed there.

    > java.lang.IllegalStateException: The context has been disposed.

Leave a Reply