Part 5: Developing CLI application with Spring Shell

Domagoj Madunić
Agency04
Published in
8 min readJun 11, 2019

In this post, we are going to configure Spring Security for our Spring Shell clidemo application. Spring Security is a very powerful library, rich in features, with support for a wide range of security mechanisms, from JAAS, CAS, to OAuth2 and many others, and has become a defacto standard security library for Spring applications. There are tons of available tutorials and examples of how to configure spring security for web applications. However, there are only a few workable tutorials on how to configure it for Java standalone applications (non-web applications).

What will we be building in this post?

If you run clidemo application and type help you should see the list of available commands, as displayed below:

CLI-DEMO:>help
AVAILABLE COMMANDS
Built-In Commands
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.
Echo Command
echo: Displays greeting message to the user whose name is supplied
progress-bar: Displays progress bar
progress-counter: Displays progress counter (with spinner)
progress-spinner: Displays progress spinner
Table Examples Command
sample-tables: Display sample tables
table-formatter-demo: Table formatter demo
User Command
create-user: Create new user with supplied username
update-all-users: Update and synchronize all users in local database with external source
user-details: Display details of user with supplied username
user-list: Display list of users

At the moment anyone who starts clidemo application can access every one of these commands. What we would like to achieve is that only those users (CliUser) who have signed in with their credentials (username/password) can access user-details and user-list commands, and only those signed-in users who have their property superuser set to TRUE can access create-user, user-details, and update-all-users commands.

As the first step of this post, add the spring security dependency to an existing build.gradle file:

dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.springframework.shell:spring-shell-starter:2.0.1.RELEASE'
implementation 'org.springframework.security:spring-security-config'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Restrict User Commands Access

Spring Shell has a built-in mechanism for controlling which commands are available to the user, due to the current internal state of the application, and which are not. This feature is called Dynamic Command Availability. Basically, it works as follows: with each command method one control method can be associated, that will be responsible to decide whether this method is available or not. For details please consult the official Spring Shell documentation on the following link.

We are going to use this Spring Shell feature to protect our user commands, that is to make them available only to the signed in users. For start, let us create a new abstract class SecuredCommand, with code as shown in the snippet below:

package com.ag04.clidemo.command;

import org.springframework.shell.Availability;
import org.springframework.shell.standard.ShellMethodAvailability;

public abstract class SecuredCommand {

public Availability isUserSignedIn() {
return Availability.unavailable("you are not signedIn. Please sign in to be able to use this command!");
}

}

The current implementation of isUserSignedIn() method will always simply return result indicating to the Spring Shell that command is not available, we will fix this later. Now declare UserCommand class to extend SecuredCommand class, and annotate all command methods in it with the following annotation:

@ShellMethodAvailability("isUserSignedIn")

Modified UserCommand class should, in the end, reassemble the following code snippet:

@ShellComponent
public class UserCommand extends SecuredCommand {
// properties omitted @ShellMethod("Display list of users")
@ShellMethodAvailability("isUserSignedIn")
public void userList() {
// method body omitted ...
}
@ShellMethod("Create new user with supplied username")
@ShellMethodAvailability("isUserSignedIn")
public void createUser(@ShellOption({"-U", "--username"}) String username) {
// method body omitted ...
}
...}

Rebuilding and running clidemo help command now produces the following output:

CLI-DEMO:>help
AVAILABLE COMMANDS
Built-In Commands
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.
Echo Command
echo: Displays greeting message to the user whose name is supplied
progress-bar: Displays progress bar
progress-counter: Displays progress counter (with spinner)
progress-spinner: Displays progress spinner
Table Examples Command
sample-tables: Display sample tables
table-formatter-demo: Table formatter demo
User Command
* create-user: Create new user with supplied username
* my-details: Display details of signedIn user
* update-all-users: Update and synchronize all users in local database with external source
* user-details: Display details of user with supplied username
* user-list: Display list of users
Commands marked with (*) are currently unavailable.
Type `help <command>` to learn more.

As you can see, all user commands are marked with asterix (*) signaling that they are not available at the moment. For example, an attempt to run user-list command would result in the following response:

CLI-DEMO:>user-list
Command ‘user-list’ exists but is not currently available because you are not signedIn. Please sign in to be able to use this command!
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.

Now that user commands are “protected” in this manner, we can proceed with implementing a real spring security protection.

Configuring Spring Security

First, we will change the existing SecuredCommand class and implement real isUserSignedIn() method, as shown in the code below:

If SecurityContextHolder contains valid Authentication object this method returns positive result (access to the command is granted), otherwise negative (access to the command is denied). For more on SecurityContextHolder see the following link. Now, only signed in users will have access to the user commands.

The next step is to configure spring security for clidemo application. At the core of our configuration is custom ClidemoUserDetailsService implementation of Spring security UserDetailsService interface. Create this class with the code from the following code snippet:

For more on UserDetailsService interface see this article. With this class in place, we can proceed and create a separate spring configuration file that will hold all spring security related configurations. To do so, create SpringSecurityConfig class as shown below:

Now it is time to enable our users to actually sign in. For that purpose, we will create a new command that will prompt users to provide their username and password. Create new SigninCommand class as shown in the code below:

Rebuild/rerun clidemo and try to signin with the following credentials: lmodric / midfielder10, and you should be greeted with welcome message as shown in the picture below.

User successfully signed in to clidemo application.

Also, running a help command now would display that all user commands are available to currently signed in user (lmodric).

NOTE: other available user data can be found in src/main/resources/cli-user.json file.

Restrict user access based on the granted authorities

What remains to be implemented is to enable only users whose property superuser is set to TRUE to have access to create-user, user-details, and update-all-users commands. As can be seen from the implementation of CliudemoUserDetailsService all users are granted ROLE_USER and only those with superuser property are also granted ROLE_ADMIN. What remains to be done is:

  1. Add a new method to SecuredCommand that will check if user has ROLE_ADMIN among his granted authorities.
  2. Associate this new method with all relevant user command methods.

To do so, first, remove all @ShellMethodAvailability annotations from the UserCommand class. Then, modify SecuredCommand class as shown in the code snippet below:

Spring Shell provides several methods of associating control methods with command methods. In the previous implementation, we have annotated command methods with the @ShellMethodAvailability annotation that contained the name of the control method (ie. isUserSignedIn) that should be invoked to determine whether the particular command is available or not.

@ShellMethodAvailability("isUserSignedIn")

In the final implementation, this relationship is reversed! We have instead opted to annotate control methods with the @ShellMethodAvailability annotation and provided the list of command names(!), NOT method names, to which particular method should control access. Thus the method isUserAdmin() is configured to check if user is allowed to access create-user, user-details, and update-all-users commands.

@ShellMethodAvailability({"create-user", "update-all-users", "user-details"})
public Availability isUserAdmin() {
...
}

In larger applications, this approach is much more practical, since all command availability configurations are found in a single place, and it is rather easy to determine which method controls the access to which command.

Now if you want to play with this new configuration rebuild and rerun clidemo, sign in with the credentials: nacho/defender6. After attempting to access update-all-users command you should see the following output.

User is denied access to command due to lack of sufficient security privileges.

This completes the fifth and final part of this blog series. Of course, many more topics remain to be tackled but armed with the knowledge of this and previous posts I have no doubt that you will be able to overcome any challenges encountered while using Spring Shell library. I do hope that you have found this series of posts satisfying and that its content will prove helpful in your future (CLI) projects.

Rest of the series

Part 1: Conveying contextual messages to the users in the CLI application

Part 2: Capturing user’s input in the CLI application

Part 3: Displaying the progress of CLI command execution with the use of counters, spinners and progress bars

Part 4: Displaying the data with the use of tables in a Spring Shell based CLI application

Part 5: Securing CLI application with Spring Security

Sample Code:

Entire source code for this tutorial is available at GitHub repository:

https://github.com/dmadunic/clidemo

Additional resources:

Spring Shell project site:

https://projects.spring.io/spring-shell/

Spring Shell official documentation:

https://docs.spring.io/spring-shell/docs/current-SNAPSHOT/reference/htmlsingle/

https://docs.spring.io/spring-shell/docs/current/api/

Spring Security official documentation:

https://spring.io/projects/spring-security

Thank you for reading! I do hope you enjoyed it and please share if you did.

--

--

Domagoj Madunić
Agency04

CEO and founder of Agency04 — Software development company based in Zagreb. Passionate about software development and 17th century Mediterranean history.