[JAVA] I tried to manage login information with JMX

It's a title like "I tried dancing", but it's a serious article.

A while ago, I researched JMX in a project I participated in, and I thought that if I used this, I wouldn't have to create a setting screen for simple settings. I think it's hard to recommend to customers, but I thought that it would be possible to reduce the man-hours for creating setting screens for in-house products, so I made an app that can manage login information easily and looked at the usability.

environment

This time it is a simple function, so I do not use DB. I used Spring to create a simple web application that just logs in. The libraries used are as follows.

Login management specifications

I will briefly describe the specifications of login management.

--You can browse the list of logged-in users. --Maximum number of logins can be set. --ID lock can be implemented.

This is enough for the login management function. It's annoying to implement too many features.

About server-side implementation

I created a simple web application that just logs in. The implementation is a very common web application using Spring MVC. It is implemented so that the logged-in information can be viewed with JMX.

Creating an MBean interface

I have created an MBean to manage login information. First, create the interface.

public interface LoginMonitorMBean {

    public static final String NAME = "examples.jmx:type=LoginMonitoring";

    public static final String LUI_ITEM_ID = "id";

    public static final String LUI_ITEM_NAME = "name";

    public int getLoginCount();

    public CompositeData[] getLoginInfos();

    void addLoginInfo(CompositeData loginUserInfo);

    public void removeLoginInfo(int id);

    public void resetLoginInfo();

    public int getMaxLoginCount();

    public void setMaxLoginCount(int count);

    public int[] getLoginLockIds();

    public void addLoginLockId(int id);

    public void removeLoginLockId(int id);

    public void resetLoginLockId();

    public static ObjectName createObjectName() {
        try {
            return new ObjectName(LoginMonitorMBean.NAME);
        } catch (MalformedObjectNameException e) {
            throw new IllegalArgumentException(e);
        }
    }

}

It seems that you must always add MBean to the end of the interface name. Ignore constants and static methods. It is a group of methods that other methods can be operated from jconsole. A description of each method is given below.

Method name Description
getLoginCount You can get the number of logins.
getLoginInfos You can get the ID and name of the logged-in user.
addLoginInfo You can add login information.
removeLoginInfo Deletes the login information of the specified ID.
resetLoginInfo Reset your login information.
getMaxLoginCount You can get the maximum number of logins.
setMaxLoginCount You can set the maximum number of logins.
getLoginLockIds You can get an array of locked IDs.
addLoginLockId You can add an ID to lock.
removeLoginLockId Delete the locked ID.
resetLoginLockId Reset the locked ID.

Creating a management class

Create a class that implements the interface you just created.

public class LoginMonitor implements LoginMonitorMBean {
}

An error will occur if the management class does not have a class name that excludes MBean from the interface name. Although the implementation content is omitted, it is basically a simple implementation that holds and acquires login information, lock ID, etc. in the class field.

MBean registration

Register the MBean that holds the login information. Registration is done when the Web application is started.

public class StartupBean {

    private static Logger log = LoggerFactory.getLogger(StartupBean.class);

    @PostConstruct
    public void initAfterStartup() {
        try {
            log.info("MBean registration process");
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            mbs.registerMBean(new LoginMonitor(), LoginMonitorMBean.createObjectName());
        } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
            throw new IllegalStateException(e);
        }
    }

}

About the class that holds login information

Data contents cannot be viewed from jconsole in data classes (DTO-like classes) that implement general setters / getters. Use javax.management.openmbean.CompositeData to make it viewable from jconsole etc.

Let's look at an example of retaining login information.

public class LoginUserInfo {

    private static final String ID = LoginMonitorMBean.LUI_ITEM_ID;

    private static final String NAME = LoginMonitorMBean.LUI_ITEM_NAME;

    private final int id;

    private final String name;

    public LoginUserInfo(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void addMBean(LoginMonitorMBean mbean) throws OpenDataException {
        CompositeType compositeType = new CompositeType(
                "LoginUserInfo",
                "Data type that holds login user information",
                new String[] { ID, NAME },
                new String[] { "Login user ID", "Login username" },
                new OpenType[] { SimpleType.INTEGER, SimpleType.STRING });
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put(ID, id);
        dataMap.put(NAME, name);
        mbean.addLoginInfo(new CompositeDataSupport(compositeType, dataMap));
    }

    public static int getMBeanId(CompositeData data) {
        return (Integer) data.get(ID);
    }

}

This is an example where the addMBeanmethod registers the data ofjavax.management.openmbean.CompositeData. This LoginUserInfois a simple data class just to hold common login IDs and login usernames. The ID used at login and the user name obtained from the database etc. (fixed value because this is a simple application) are retained. The information in this class is converted tojavax.management.openmbean.CompositeDataSupport, which is an implementation class of the javax.management.openmbean.CompositeData` interface. By doing this, you can browse from jconsole as follows.

image.png

About login function

The login function is simple. After logging in, the MBean managed by JMX is taken out, the login limit and lock ID are checked, and if there is no error, the login information is registered in the MBean. The implemented LoginController is as follows.

@Controller
public class LoginController {

    @PostMapping("/login")
    public String login(@Validated @ModelAttribute LoginForm form, BindingResult result, Model model) {
        //Input error check
        if (result.hasErrors()) {
            model.addAttribute("validationError", "Input error");
            return "login";
        }

        //Login check
        if (100 > form.getLoginId() && 300 < form.getLoginId()) {
            model.addAttribute("validationError", "Login error");
            return "login";
        }
        if (!"testtest".equals(form.getLoginPasswd())) {
            model.addAttribute("validationError", "Login error");
            return "login";
        }

        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        LoginMonitorMBean mbean = JMX.newMBeanProxy(mbs, LoginMonitorMBean.createObjectName(), LoginMonitorMBean.class);

        //Login number check
        if (mbean.getMaxLoginCount() > 0 && mbean.getLoginCount() >= mbean.getMaxLoginCount()) {
            model.addAttribute("validationError", "Login number limit");
            return "login";
        }

        //ID lock check
        if (Arrays.stream(mbean.getLoginLockIds()).filter(lockId -> lockId == form.getLoginId()).findFirst()
                .isPresent()) {
            model.addAttribute("validationError", "ID locked");
            return "login";
        }

        //Login information registration
        try {
            LoginUserInfo info = new LoginUserInfo(form.getLoginId(),
                    String.format("Test user (%d)", form.getLoginId()));
            info.addMBean(mbean);
        } catch (OpenDataException e) {
            e.printStackTrace();
            model.addAttribute("validationError", "Internal system error");
            return "login";
        }

        model.addAttribute("loginCount", mbean.getLoginCount());
        return "home";
    }

}

Startup parameters

Since this web application is Spring Boot, it can be started like the following.

java -jar jmx-examples-1.0.0.war

In order to check the registered MBean remotely, it is necessary to set the startup parameters as follows.

java -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar jmx-examples-1.0.0.war
Startup parameters Description
com.sun.management.jmxremote.port You can specify the port number of the MBean server.
com.sun.management.jmxremote.authenticate This time, because it is a simple function, the authentication function when accessing the MBean server is disabled.
com.sun.management.jmxremote.ssl This time, for the sake of simplicity, SSL is disabled when accessing the MBean server.

If you start it as above, you can access it by remote process from jconsole etc.

About client-side implementation

It can be accessed from both local and remote processes from jconsole, but I tried to create a client application that accesses the process ID and remotely in the same way.

Refer to the following page for how to connect JMX. 2 Monitoring and Management Using JMX Technology

Remote access

In order to access the MBean server remotely, you need to specify the startup parameters for com.sun.management.jmxremote.port. The following address is generated using the port number specified in the above startup parameter.

service:jmx:rmi:///jndi/rmi://localhost:5000/jmxrmi

Then use the above address to connect to the MBean server.

String connectAddress = "service:jmx:rmi:///jndi/rmi://localhost:5000/jmxrmi";
try (JMXConnector jmxc = JMXConnectorFactory.connect(new JMXServiceURL(connectAddress))) {
    MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
    LoginMonitorMBean lmbean = JMX.newMBeanProxy(mbsc, LoginMonitorMBean.createObjectName(),
            LoginMonitorMBean.class);
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Local access

A process ID is required for local access. If you know the process ID, you can connect, so you do not need to specify the startup parameter.

String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
VirtualMachine vm = VirtualMachine.attach(pid);
String connectorAddress;
try {
    connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
    if (connectorAddress == null) {
        vm.startLocalManagementAgent();
        connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
    }
} finally {
    vm.detach();
}

Since the character string for connection can be obtained with the above logic, it is possible to connect to the local process with the following description described even for remote connection using that character string.

JMXConnector jmxc = JMXConnectorFactory.connect(new JMXServiceURL(connectAddress))

JVM information

By connecting to the MBean server, you can get the JVM information in addition to the registered MBean. The MBean for acquiring the virtual machine name and process ID and the MBean for acquiring heap information can be acquired with the following description.

try (JMXConnector jmxc = JMXConnectorFactory.connect(new JMXServiceURL(connectAddress))) {
    MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

    //Display runtime information for the Java virtual machine
    RuntimeMXBean rmxbean = ManagementFactory.getPlatformMXBean(mbsc, RuntimeMXBean.class);
    //Get process ID etc. from rmx bean

    //Display memory information
    MemoryMXBean mmxbean = ManagementFactory.getPlatformMXBean(mbsc, MemoryMXBean.class);
    MemoryUsage memoryUsage = mmxbean.getHeapMemoryUsage();
    //Get heap information from memoryUsage

    //Display OS information
    OperatingSystemMXBean omxbean = ManagementFactory.getPlatformMXBean(mbsc, OperatingSystemMXBean.class);
    //Get OS information from omxbean
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Summary

When I tried using JMX, it was so easy and convenient that I didn't need to create a setting screen with only one text box. Of course, if you have performance problems and setting information in the DB, you may consider whether you can connect to the DB and how much the load is, but you can easily change the settings with jconsole, so the man-hours to create the screen I felt that it could be reduced. The application created this time is stored in the following repository, so please have a look.

jmx-examples

Recommended Posts

I tried to manage login information with JMX
I tried to manage struts configuration with Coggle
I tried to interact with Java
I tried to get started with WebAssembly
I tried to implement ModanShogi with Kinx
I tried to verify AdoptOpenJDK 11 (11.0.2) with Docker image
I tried to make Basic authentication with Java
I tried to break a block with java (1)
I tried what I wanted to try with Stream softly.
I tried to implement file upload with Spring MVC
I tried to read and output CSV with Outsystems
I tried to implement TCP / IP + BIO with JAVA
[Java 11] I tried to execute Java without compiling with javac
I started MySQL 5.7 with docker-compose and tried to connect
I tried to get started with Spring Data JPA
I tried to make a login function in Java
I tried to draw animation with Blazor + canvas API
I tried to implement Stalin sort with Java Collector
roman numerals (I tried to simplify it with hash)
I tried UPSERT with PostgreSQL.
I tried BIND with Docker
I tried to verify yum-cron
I tried to summarize the points to consider when acquiring location information with the iOS application ③
I tried to summarize the points to consider when acquiring location information with the iOS application ①
I tried to summarize the points to consider when acquiring location information with the iOS application ②
I tried to make an introduction to PHP + MySQL with Docker
I tried to create a java8 development environment with Chocolatey
I tried to modernize a Java EE application with OpenShift.
I tried to increase the processing speed with spiritual engineering
[Rails] I tried to create a mini app with FullCalendar
I tried to link chat with Minecraft server with Discord API
[Rails] I tried to implement batch processing with Rake task
I tried to automate LibreOffice Calc with Ruby + PyCall.rb (Ubuntu 18.04)
I tried to create a padrino development environment with Docker
I tried to get started with Swagger using Spring Boot
I tried upgrading from CentOS 6.5 to CentOS 7 with the upgrade tool
I tried to be able to pass multiple objects with Ractor
I tried to chew C # (indexer)
I tried using JOOQ with Gradle
I tried morphological analysis with MeCab
I tried to summarize iOS 14 support
I tried UDP communication with Java
I tried to explain the method
I tried GraphQL with Spring Boot
I tried to summarize Java learning (1)
I tried to understand nil guard
I tried Flyway with Spring Boot
I tried to summarize Java 8 now
I tried to chew C # (polymorphism: polymorphism)
I tried customizing slim with Scaffold
I tried to explain Active Hash
I tried to solve the problem of "multi-stage selection" with Ruby
I tried to build the environment of PlantUML Server with Docker
I tried connecting to MySQL using JDBC Template with Spring MVC
I tried to implement the image preview function with Rails / jQuery
I tried to build an http2 development environment with Eclipse + Tomcat
I tried to implement flexible OR mapping with MyBatis Dynamic SQL
I tried connecting to Oracle Autonomous Database 21c with JDBC Thin
I tried to reimplement Ruby Float (arg, exception: true) with builtin
I tried to make an Android application with MVC now (Java)
I tried to check the operation of gRPC server with grpcurl