9.5. Using a Custom Login Module

This section discuses implementing a custom login module. If one of the default login modules meets your needs, you can go with that and skip this section. On the other hand, if you have special requirements (such as a lockout after a certain number of failed logins) or if you need to authenticate against a security provider not covered by one of the default realms (such as a Single Sign-On product), then this is the section for you!

9.5.1. Implementing a Custom LoginModule

A custom login module needs to implement javax.security.auth.spi.LoginModule. The login module has two main responsibilities:

  1. Prompt for the user's login information (often a username and password, but sometimes a certificate, etc.) and validate whether that represents a valid account

  2. If the account is valid, populate a javax.security.auth.Subject with appropriate java.security.Principal values.

To make it that far, the LoginModule must define the principal classes it intends to use. For example, a login module that provides principals for the username and the user's home state might declare principals like this:

Example 9.8. Custom Realm Principal Classes

public class UserPrincipal implements Principal, Serializable {
    private String name;

    public UserPrincipal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String toString() {
        return name;
    }

    public boolean equals(Object o) {
        return o instanceof UserPrincipal &&
               ((UserPrincipal)o).name.equals(name);
    }

    public boolean hashCode() {
        return name.hashCode();
    }
}

public class StatePrincipal implements Principal, Serializable {
    private String state;

    public StatePrincipal(String state) {
        state = state;
    }

    public String getName() {
        return state;
    }

    public String toString() {
        return state;
    }

    public boolean equals(Object o) {
        return o instanceof StatePrincipal &&
               ((StatePrincipal)o).state(state);
    }

    public boolean hashCode() {
        return state();
    }
}

Then the LoginModule that uses those Principal classes might look like this:

Example 9.9. Custom Realm LoginModule

public class MyLoginModule implements LoginModule {
    private Subject subject;
    private CallbackHandler handler;
    private String user;

    public void initialize(Subject subject,
                           CallbackHandler handler,
                           Map sharedState, Map options) {
        this.subject = subject;
        this.handler = handler;
        // Any options passed in the ConfigurationEntry GBean
        // will end up in the options Map here, and can be
        // saved to be used in the other methods
    }

    public boolean login() throws LoginException {
        NameCallback name = new NameCallback("User name");
        PasswordCallback pwc = new PasswordCallback("Password",
                                                    false);
        handler.handle(new Callback[]{name, pwc});
        user = name.getName();
        String pw = new String(pwc.getPassword());
        // validate the account
    }

    public boolean commit() throws LoginException {
        subject.add(new UserPrincipal(user));
        String state = ...;
        subject.add(new StatePrincipal(state));
        return true;
    }

    ...
}

9.5.2. Packaging and Deploying a Custom LoginModule

To package a custom login module, you should package the login module and principals into a JAR, and add it to the Geronimo repository (in a file such as geronimo/repository/login/jars/my-login-module-1.0.jar). Then you can configure the custom login module in the normal way -- just add a login modules configuration entry with the login-module-class set to the new class (see Figure 9.14, “Security: New Login Module Configuration”).

Both the top-level deployment procedure described in Section 9.3.1, “Server-Wide Security Realms” and the application-level deployment procedure described in Section 9.3.2, “Application-Scoped Security Realms” support the dependency element, which is used to include the custom login module JAR on the ClassPath for the configuration:

<dependency>
  <uri>login/jars/my-login-module-1.0.jar</uri>
</dependency>

<!-- Normal security realm declaration here -->
<gbean ...