I'd like to add a custom LoginModule to Karaf Jaas framework, and create a new realm that will use it.
How can I do this?
I have my own solution as follows, implemented using Declarative Services.
First of all, create a new LoginModule
implementation extending org.apache.karaf.jaas.modules.AbstractKarafLoginModule
.
public class CustomLoginModule extends AbstractKarafLoginModule
{
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options)
{
super.initialize(subject, callbackHandler, options);
// Use the `options` parameter for extra information that will be used
// inside the module; this is passed from the JaasRealm service
}
public boolean login() throws LoginException
{
// prepare callback objects and get the authentication information
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("Username: ");
callbacks[1] = new PasswordCallback("Password: ", false);
try {
callbackHandler.handle(callbacks);
}
catch (Exception e) {
throw new LoginException(e.getMessage());
}
user = ((NameCallback) callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
if (tmpPassword == null)
tmpPassword = new char[0];
String password = new String(tmpPassword);
// Do the custom authentication and throw `LoginException` if the user
// is not valid. Get the roles and groups that the user is assigned to.
// .......
// Add roles and groups along with the user information to the `principals`
principals = new HashSet<>();
principals.add(new UserPrincipal(user));
for (String role : roles)
principals.add(new RolePrincipal(role));
for (String group: groups)
principals.add(new GroupPrincipal(group));
return true;
}
public boolean abort() throws LoginException
{
return true;
}
public boolean logout() throws LoginException
{
subject.getPrincipals().removeAll(principals);
principals.clear();
return true;
}
}
Second; in order to use this class as the LoginModule of a new realm, we have to register a new org.apache.karaf.jaas.config.JaasRealm
service and set our own login module. We will put this file in the same bundle with the CustomLoginModule
.
@Component(immediate = true)
public class CustomJaasRealmService implements JaasRealm
{
public static final String REALM_NAME = "customRealm";
private AppConfigurationEntry[] configEntries;
@Activate
public void activate(BundleContext bc)
{
// create the configuration entry field using ProxyLoginModule class
Map<String, Object> options = new HashMap<>();
configEntries = new AppConfigurationEntry[1];
configEntries[0] = new AppConfigurationEntry(ProxyLoginModule.class.getName(),
LoginModuleControlFlag.SUFFICIENT, options);
// actual LoginModule class name will be passed using the options object
options.put(ProxyLoginModule.PROPERTY_MODULE, CustomLoginModule.class.getName());
// put bundle id of the LoginModule and bundlecontext of it
// (in this case, it is the same bundle)
// This is a neat trick to adapt to OSGI classloader
long bundleId = bc.getBundle().getBundleId();
options.put(ProxyLoginModule.PROPERTY_BUNDLE, String.valueOf(bundleId));
options.put(BundleContext.class.getName(), bc);
// add extra options if needed; for example, karaf encryption
// ....
}
@Override
public AppConfigurationEntry[] getEntries()
{
return configEntries;
}
// return the name and the rank of the realm
@Override
public String getName()
{
return REALM_NAME;
}
@Override
public int getRank()
{
return 0;
}
}