3

I am quite new to Java EE and have been having a lot of trouble just getting started on the application I want to create. What I would like is a Swing application client that connects to an EJB project. I am using Glassfish v3.1.1. What I have so far are two stateless beans, one of which is secured using @DeclareRoles and a JDBC realm in Glassfish, and the beginnings of a client.

When the client is run, you are able to select a username, type a password, and thus login. If you use the correct password, everything works (the client console spits on some "secure" information). If you, however, type the incorrect password, you are permanently locked out. The InitialContext.lookup does not call the CallbackHandler again to check the new password, it continues using the incorrect credentials.

Can someone please tell me how to do this correctly? Am I using the correct method for this situation - there is an enormous amount of info on the web but basically 0 examples of what I am trying to do? Everything seems to apply only to J2EE or Servlets! Here is some relevant code.

glassfish-ejb-jar.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-ejb-jar PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 EJB 3.1//EN" "http://glassfish.org/dtds/glassfish-ejb-jar_3_1-1.dtd">
<glassfish-ejb-jar>
  <security-role-mapping>
    <role-name>Admin</role-name>
    <group-name>Admin</group-name>
  </security-role-mapping>
  <security-role-mapping>
    <role-name>Employee</role-name>
    <group-name>Employee</group-name>
  </security-role-mapping>
  <enterprise-beans>
    <ejb>
      <ejb-name>LoginBean</ejb-name>
      <jndi-name>ejb/machineryhub/LoginService</jndi-name>
    </ejb>
    <ejb>
      <ejb-name>EmployeeBean</ejb-name>
      <jndi-name>ejb/machineryhub/EmployeeService</jndi-name>
      <ior-security-config>
        <as-context>
          <auth_method>username_password</auth_method>
          <realm>machineryhub</realm>
          <required>true</required>
        </as-context>
      </ior-security-config>
    </ejb>
  </enterprise-beans>
</glassfish-ejb-jar>

Will I need to add the <ior-security-config> block to every secured bean I create?

application-client.xml:

<?xml version="1.0" encoding="UTF-8"?>
<application-client version="6" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_6.xsd">
  <display-name>MachineryHub</display-name>
  <ejb-ref>
    <ejb-ref-name>LoginBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>machineryhub.service.LoginService</remote>
  </ejb-ref>
  <ejb-ref>
    <ejb-ref-name>EmployeeBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>machineryhub.service.EmployeeService</remote>
  </ejb-ref>
  <callback-handler>machineryhub.LoginCallbackHandler</callback-handler>
</application-client>

machineryhub.LoginCallbackHandler:

public class LoginCallbackHandler implements CallbackHandler {

  @Override
  public void handle(Callback[] clbcks) throws IOException, UnsupportedCallbackException {
    LoginFrame l = LoginFrame.instance;
    for (Callback cb : clbcks) {
      if (cb instanceof NameCallback) {
        NameCallback ncb = (NameCallback) cb;
        ncb.setName(l.usernameCombo.getSelectedItem().toString());
      } else if (cb instanceof PasswordCallback) {
        PasswordCallback pcb = (PasswordCallback) cb;
        pcb.setPassword(l.passwordText.getPassword());
      } else {
        throw new UnsupportedCallbackException(cb);
      }
    }
  }
}

And now for the long one, the swing application client.

machineryhub.LoginFrame

public class LoginFrame extends JFrame implements ActionListener {

  public static LoginFrame instance;

  public static void main(String[] args) {
    // Handle uncaught exceptions in the main and Swing threads
    ExceptionHandler.registerExceptionHandler();

    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {
        try {
          UIManager.setLookAndFeel(new SubstanceMistSilverLookAndFeel());
          JFrame.setDefaultLookAndFeelDecorated(true);
          JDialog.setDefaultLookAndFeelDecorated(true);
          (new LoginFrame()).setVisible(true);
        } catch (final Exception exception) {
          ExceptionHandler.handle(Thread.currentThread(), exception);
        }
      }
    });
  }
  public JComboBox usernameCombo;
  public JPasswordField passwordText;
  private JButton loginButton;

  public LoginFrame() {
    // Window Setup

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setTitle("Login :: MachineryHub");
    this.setLocationRelativeTo(null);
    this.setIconImages(IconFactory.application_images);

    // Create GUI

    createGui();
    usernameCombo.requestFocusInWindow();
    LoginFrame.instance = this;
  }

  private void createGui() {
    // Content Pane
    final JPanel contentPanel = new JPanel();

    List<String> usernames = getLoginService().getUsernames();
    Collections.sort(usernames);
    usernameCombo = new JComboBox(usernames.toArray());
    passwordText = new JPasswordField(15);
    passwordText.setActionCommand("Login");
    passwordText.addActionListener(this);

    loginButton = new JButton("Login", IconFactory.getImageIcon(IconFactory.Icon.KEY, 16));
    loginButton.setActionCommand("Login");
    loginButton.addActionListener(this);

    GroupLayout layout = new GroupLayout(contentPanel);
    contentPanel.setLayout(layout);
    layout.setAutoCreateContainerGaps(true);
    layout.setAutoCreateGaps(true);

    layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING).addComponent(usernameCombo).addGroup(layout.createSequentialGroup().addComponent(passwordText).addComponent(loginButton)));

    layout.setVerticalGroup(layout.createSequentialGroup().addComponent(usernameCombo, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE).addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE).addComponent(passwordText).addComponent(loginButton)));

    this.setContentPane(contentPanel);
    this.pack();
  }

  @Override
  public void actionPerformed(final ActionEvent e) {
    if (e == null || e.getActionCommand() == null) {
      return;
    }

    if (e.getActionCommand().equals("Login")) {
      loginButton.setEnabled(false);
      passwordText.setEnabled(false);
      usernameCombo.setEnabled(false);
      loginButton.setIcon(IconFactory.getImageIcon(IconFactory.SpecialImage.LOADING));

      try {
        Context c = new InitialContext();

        EmployeeService es = (EmployeeService) c.lookup("ejb/machineryhub/EmployeeService");
        System.out.println("Number of employees: " + es.getAllEmployees().size());
        this.dispose();
      } catch (NamingException exception) {
        loginButton.setEnabled(true);
        passwordText.setEnabled(true);
        usernameCombo.setEnabled(true);
        loginButton.setIcon(IconFactory.getImageIcon(IconFactory.Icon.KEY, 16));
        JOptionPane.showMessageDialog(LoginFrame.this, "Login Error: " + exception.getMessage(), "Login Error! :: MachineryHub", JOptionPane.ERROR_MESSAGE);
      }
    }
  }

  private LoginService getLoginService() {
    try {
      Context c = new InitialContext();
      return (LoginService) c.lookup("ejb/machineryhub/LoginService");
    } catch (NamingException ne) {
      throw new RuntimeException(ne);
    }
  }
}
raistlin0788
  • 406
  • 4
  • 12
  • Log how many times `LoginCallbackHandler` is called. – palacsint Oct 05 '11 at 20:47
  • @palacsint It is only called once, on the first attempt to log in. The second attempt fails with no call to `LoginCallbackHandler`. I'm pretty confident this is where the problem lies, but I have no idea how to force a second authentication attempt. – raistlin0788 Oct 06 '11 at 01:53
  • http://stackoverflow.com/questions/2758248/how-does-java-logincontext-login-work ? – palacsint Oct 06 '11 at 19:13
  • @palacsint When I rework my code to use `LoginContext`, I have a different problem. `LoginContext.login` seems to work (no exceptions), but when I do the `lookup`, it prompts me for username and password again using the built-in Glassfish prompt. This of course brings me right back to the original problem of preventing me from logging in after one failure. Based on [this message](http://markmail.org/message/iu3zzoiklvunxegy#query:+page:1+mid:qu37ezhbyudsst3a+state:results) , I am lead to believe that the client container should take care of everything except the callback. – raistlin0788 Oct 06 '11 at 21:06

1 Answers1

3

I'm not positive this is the best or recommended way to solve this problem, but I have found a way to do what I need. The solution lies in using the ProgrammaticLogin class. I removed the LoginCallbackHandler class and the reference from application-client.xml. Then in the login code, just before creating InitialContext, I used the following very simple two lines:

ProgrammaticLogin pl = new ProgrammaticLogin();
pl.login(usernameCombo.getSelectedItem().toString(), passwordText.getPassword());

And this seems to work regardless of how many times I enter the wrong password (you could put a limit on this as well with a simple counter). I feel a little stupid for taking so long to figure this out, but this class didn't show up in Netbeans so I assumed it was something that was no longer valid in Java EE 6. However, it is simply a matter of adding Glassfish/modules/security.jar to the libraries for it to show up.

raistlin0788
  • 406
  • 4
  • 12
  • 1
    Thanks for sharing this. The reason why this class didn't show up in Netbeans is that it's an application server dependent solution so you cannot use with another appservers (like JBoss etc.). – palacsint Oct 10 '11 at 17:32