Search code examples
osgijaaskaraf

How to add a custom LoginModule to Karaf Jaas security framework?


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?


Solution

  • 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;
        }   
    }