1

We have an old client application that is deployed on JBoss 4.2.3 and written in JAVA EJB2. It has classes that depend on JBoss's security libraries, an Oracle DataSource and ANT as build method. Now there is a need of upgrading the application server because JBoss 4 no longer has life support and we are required to upgrade to Wildfly(Version 8.2 in our case). Naturally we are having a lot of problems during the process and working tirelessly just to go no further from where we are.

I just would like to get community's thoughts on this process. Is it worth the effort to upgrade JBoss or should one just re-write the client from scratch with a newer technology e.g Spring? What is the best practice in a situation like this?

By the way this client is not a big application, it is used by only 6 users.

As proposed by Michele Dorigatti, Here are some more details on the project:

  • I already spent an estimate of 15 m/d on the upgrade proces.
  • We are required to implement the solution in 3 weeks from now on.
  • The app itself isn't that large, it consists of 1 login screen and 1 main view. There are several functionalities which would make up to maybe 15-20 use cases.
  • The team for the project consists of 2 developers (One being me), who have another project on their hand.
  • The app functions mainly on Oracle stored procedures and works maybe on 5-10 DB tables.

Also here is an example code snippet from the app

package tr.com.splogin;

import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.AbstractServerLoginModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;
import java.security.Principal;
import java.security.acl.Group;
import java.sql.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class SPLoginModule extends AbstractServerLoginModule {

private static final int USER_LOCKEDOUT = 23;
private static final int USER_VALFAIL = 24;
private static final int USER_MAXATTEMPTS = 25;

private static final String ROLE_GROUP_NAME = "Roles";
private static final String ID_GROUP_NAME = "Id";

private static Logger logger = LoggerFactory.getLogger(SPLoginModule.class);

private static final SimplePrincipal GUEST = new SimplePrincipal("guest");
private static boolean initialized = false;
private static boolean initFailed = false;
private static Connection conn;
private static CallableStatement cs;
private static PreparedStatement ps;
private static ResultSet rs;
/**
 * The principal to use when a null username and password are seen
 */
private static Principal unauthenticatedIdentity;
private static Map options;

/**
 * The roles of the authenticated user
 */
private Group[] roleSets;
/**
 * The proof of login identity
 */
private char[] credential;
/**
 * The login identity
 */
private Principal identity;

public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
    logger.info("initialize start");
    System.out.println("initialize start");
    super.initialize(subject, callbackHandler, sharedState, options);
    if (!initialized) {
        this.options = options;
        init(options);
        initialized = true;
    }
    logger.info("initialize stop");
}

private String getUsername() {
    String username = null;
    if (getIdentity() != null)
        username = getIdentity().getName();
    return username;
}

public boolean login() throws LoginException {
    System.out.println("login is called.");
    String[] info = getUsernameAndPassword();
    String username = info[0];
    String password = info[1];

    logger.info(username);
    logger.info(password);

    super.loginOk = false;

    if (username == null && password == null) {
        identity = unauthenticatedIdentity;
        Group roles = new SimpleGroup(ROLE_GROUP_NAME);
        Set groups = new HashSet();
        groups.add(roles);
        roles.addMember(GUEST);
        roleSets = new Group[groups.size()];
        groups.toArray(roleSets);
        logger.info("Authenticating as unauthenticatedIdentity=" + identity);
    }

    if (identity == null) {
        identity = new SimplePrincipal(username);
        login(username, password);
    }

    super.loginOk = true;
    logger.info("User '" + identity + "' authenticated, loginOk=" + loginOk);
    return true;
}

public Principal getIdentity() {
    return identity;
}

public Group[] getRoleSets() {
    return roleSets;
}

private void login(String username, String password) throws LoginException {
    System.out.println("login is called.");
    try {
        int userIdCode = 3;
        int resultCode = 4;
        int result, userId;

        cs.setString(1, username);
        cs.setString(2, password);
        cs.registerOutParameter(userIdCode, Types.INTEGER);
        cs.registerOutParameter(resultCode, Types.INTEGER);

        cs.execute();

        result = cs.getInt(resultCode);

        if (result == 0) {
            userId = cs.getInt(userIdCode);
            logger.info("Id: " + userId);

            Group roles = new SimpleGroup(ROLE_GROUP_NAME);
            Group id = new SimpleGroup(ID_GROUP_NAME);
            Set groups = new HashSet();
            String roleName;

            groups.add(roles);
            groups.add(id);

            ps.setInt(1, userId);
            rs = ps.executeQuery();

            id.addMember(new SimplePrincipal((new Integer(userId)).toString()));

            while (rs.next()) {
                roleName = rs.getString(1);
                logger.debug("Action: " + roleName);
                roles.addMember(new SimplePrincipal(roleName));
            }
            roles.addMember(GUEST);

            roleSets = new Group[groups.size()];
            groups.toArray(roleSets);
        } else {
            String message = new String();
            roleSets = new Group[0];

            switch (result) {
                case USER_VALFAIL:
                    System.out.println("login is failed.");
                    message = new String("Login failed");
                    break;
                case USER_LOCKEDOUT:
                    message = new String("User is locked out");
                    break;
                case USER_MAXATTEMPTS:
                    message = new String("Max number of attempts reached, user is locked out");
                    break;
                default:
                    message = new String("Unkown failed login error with code: " + result);
                    break;
            }
            logger.info("Error result code: " + result);
            logger.info("Error message: " + message);
            throw new FailedLoginException(message);
        }
    } catch (SQLException e) {
        logger.error(e.toString());
        init(options);
        if (!initFailed)
            login(username, password);
    } finally {
        try {
            if (rs != null)
                rs.close();
        } catch (SQLException e1) {
            logger.error(e1.toString());
        }
    }
}

private void init(Map options) {
    logger.info("init");
    try {
        if (cs != null)
            cs.close();
        if (ps != null)
            ps.close();
        if (conn != null)
            conn.close();
    } catch (SQLException e) {
        logger.error(e.toString());
    }

    try {
        InitialContext ctx = new InitialContext();
        DataSource ds = (DataSource) ctx.lookup("java:/OracleDS");

        conn = ds.getConnection();
        String sp_login = "{call admin_pck.pc_login(?,?,?,?)}";
        String query_user_action = "select aa.name from admin_user au,admin_role ar,admin_action aa,admin_user_role aur,admin_role_action ara,owner o where au.id=? and aur.id_admin_user=au.id and aa.id=ara.id_admin_action and ara.id_admin_role=ar.id and ar.id=aur.id_role and o.id=aur.id_owner and o.id=au.id_primary_owner order by aa.name";

        cs = conn.prepareCall(sp_login);
        ps = conn.prepareStatement(query_user_action);

        String name = (String) options.get("unauthenticatedIdentity");
        if (name != null) {
            unauthenticatedIdentity = new SimplePrincipal(name);
            logger.info("Saw unauthenticatedIdentity=" + name);
        }
        initFailed = false;
    } catch (NamingException e) {
        logger.error(e.toString());
        initFailed = true;
    } catch (SQLException e) {
        logger.error(e.toString());
        initFailed = true;
    }
}

/**
 * Called by login() to acquire the username and password strings for
 * authentication. This method does no validation of either.
 *
 * @return String[], [0] = username, [1] = password
 * @throws LoginException thrown if CallbackHandler is not set or fails.
 */
protected String[] getUsernameAndPassword() throws LoginException {
    String[] info = {null, null};
    // prompt for a username and password
    if (callbackHandler == null) {
        throw new LoginException("Error: no CallbackHandler available to collect authentication information");
    }
    NameCallback nc = new NameCallback("User name: ");
    PasswordCallback pc = new PasswordCallback("Password: ", false);
    Callback[] callbacks = {nc, pc};
    String username = null;
    String password = null;
    try {
        callbackHandler.handle(callbacks);
        username = nc.getName();
        char[] tmpPassword = pc.getPassword();
        if (tmpPassword != null) {
            credential = new char[tmpPassword.length];
            System.arraycopy(tmpPassword, 0, credential, 0, tmpPassword.length);
            pc.clearPassword();
            password = new String(credential);
        }
    } catch (java.io.IOException e) {
        throw new LoginException(e.toString());
    } catch (UnsupportedCallbackException e) {
        throw new LoginException("CallbackHandler does not support: " + e.getCallback());
    }
    info[0] = username;
    info[1] = password;
    return info;
}
}
Eren E.
  • 61
  • 9
  • Spring Boot would simplify your application. It's a powerful framework. Go for it if you have the budget. – Ctorres Dec 05 '19 at 16:18
  • Welcome! This risks to be an opinion-based question, i.e. not answerable. Could you add more precise requirements? For example, time required? It would also help if you added a rough estimation of how many human/hours you put already in it and how many you estimate you still need. – Michele Dorigatti Dec 05 '19 at 16:24
  • @Ctorres I keep that in mind, Spring Boot does indeed make it easy for the developer. Thanks for sharing your opinion. – Eren E. Dec 05 '19 at 17:00
  • @MicheleDorigatti Thanks for the advice. I added some more details. I am just a Jr developer so I am not that good at estimating efforts but tried my best. – Eren E. Dec 05 '19 at 17:03
  • 1
    I agree with @MicheleDorigatti that this will be a bit opinionated but to be honest I'd give it a shot. Wildfly 8 is old enough that the newer security models were not implemented yet and I have code that is similar to what you're showing running on a Wildfly 17 server. EJB 2 was always a pain and the configuration of that will be a challenge but it should work similarly to what you're already doing. Having said that "is it worth it" is up to you. If you can't change much to improve the architecture then I guess I question the value. – stdunbar Dec 05 '19 at 17:31
  • 2
    Yeah, it's opinionated for sure. Sounds to me like a management, and not a coding problem. What are the (new) business requirements you need to implement? Upgrading to Wildfly cannot be a business requirement. Which way you are implementing the requirement is (foremost) dependent on the team that needs to implement it and their skills. Also, I would advise to put that code on the [codereview](https://codereview.stackexchange.com/) site. – Martijn Burger Dec 05 '19 at 17:56
  • Thank you all for your valuable feedback and advices. It seems for now that the best course would be to migrate the code to Spring Boot since upgrading the jboss-version for an application that is so dependant on AS has many drawbacks. It really is a pain dealing with old technologies at this era and a good planning beforehand is a critical neccessity. – Eren E. Dec 09 '19 at 08:07
  • 1
    @MartijnBurger Actually this request came to us as a security bug since JBoss 4 does not have a life support any more. We were requested to get rid of the old version on the server that is it. But ofc our app still had to be up and running so we had to migrate our app to a different deployment structure and wildfly seemed the best option here. But now, I am thinking that with a better management, change of code technology would suit this case better. – Eren E. Dec 09 '19 at 08:16
  • @ErenE. what road did you decide for? How it went? It would be great to add some lessons learned so others could learn from it as well. – Ewoks Jul 11 '22 at 05:26

0 Answers0