Security and Swagger with Micronaut

Lock in a Gear

Couple of weeks back, I stumbled upon Micronaut. I became interested in it because of the ease of using this framework. So I wanted to spend some more time investigating it.

You can find my previous blog post for this framework here. To evaluate, I started on the next logical step; documenting and securing the microservices. I will use OAuth2 for security and Swagger for API documentation. For this I am also using in-memory H2 database with Hikari pools.

I will be creating a very simple library book check-in/ check-out system. Only logged in users will be able to access library APIs. However, I will add an anonymous API to verify how not logged in users work. Let’s start up a project in Micronaut. This will be a longer blog entry as we have quite a lot of grounds to cover. So grab your coffee and let’s begin our journey.

% mn create-app com.suturf.secure.vlibrary --build maven

The first thing is to try and add a database. For this I will use an in-memory H2 database and use Hikari to handle connection pooling. The two dependencies that will be added to pom.xml is provided below. I am using Micronaut version 1.3.4 for this trial.

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.micronaut.configuration</groupId>
    <artifactId>micronaut-jdbc-hikari</artifactId>
    <scope>runtime</scope>
</dependency>

Micronaut maintains application.yml file for all configurations. This file is present in resources directory. We need the following sections in this yaml file.

datasources:
  default:
    url: jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password: ''
    driverClassName: org.h2.Driver

Here we defined an in-memory database. We have already defined hikari connection pool library in the pom. As a result of this, connection pooling with hikari is enabled.

Repository

Now let me put some Java codes for the application we are building. I have created three repo classes for this project. One of them creates the tables in memory database, so it is not really a repository per se. However, I just wanted to keep this to show an anonymous access rule to some APIs. We can create these tables in many other ways, but I wanted an easy use case. I will come back to this later. The other two just acts as data repository for books and user tables.

@Singleton
public class DbInitRepo {
    private final static String CR_LGN_TBL =
            "create table logintbl (usr varchar(30) not null, pwd varchar(30) not null)";
    private final static String CR_LBK_TBL = "create table lbooktbl " +
            "(isbn char(14) not null, name varchar(80) not null, " +
            "author varchar(80) not null, status char(1))";
    private final static String IN_LGN_DATA = "insert into logintbl values (?, ?)";
    private final static String IN_LBK_DATA = "insert into lbooktbl values (?, ?, ?, ?)";

    @Inject
    private DataSource ds;
  
    public void createTables()
    throws SQLException {

        try (final Connection con = ds.getConnection();
             final Statement st = con.createStatement()) {
            // Create tables
            st.execute(CR_LGN_TBL);
            st.execute(CR_LBK_TBL);
        }
    }
  
    public void insertLoginData(final String uid, final String pwd)
    throws SQLException {
      /* Implementation */
    }
  
    public void insertLibraryBook(final String isbn, final String name, 
                                  final String author, final String stat)
    throws SQLException {
      /* Implementation */
    }
@Singleton
public class LibraryRepo {
    private final static String SL_ALL_BOOKS =
            "select isbn, name, author from lbooktbl order by name";
    private final static String SL_SEL_BOOKS =
            "select isbn, name, author from lbooktbl where status = ? order by name";
    private final static String UP_STT_BOOKS =
            "update lbooktbl set status = ? where isbn = ?";

    @Inject
    private DataSource ds;

    public BookListBean getAllBooks() {
      /* Implementation */
    }
  
    // Other Methods
@Singleton
public class LoginRepo {
    private final static String SL_VAL_USERS = "select usr, pwd from logintbl " +
            "where usr = ?";

    @Inject
    private DataSource ds;

    public boolean validateUser(final String usr, final String pwd)
    throws SQLException {
        /* Implementation */
    }
}

The thing of interest in all of the classes above is @Inject annotation. We are injecting the DataSource object that can be used further to get Connection objects. Rest of the class is fairly mundane. It has regular JDBC statements – DDL and DML as the case may be. That enabled JDBC in Micronaut. Fairly uncomplicated.

Now that we have a database connection, we will create a controller and expose an endpoint to create the initial database. Like I said before, this is possibly not the best use case. However later we will use this service to demonstrate how to access anonymously.

/**
 * This class is used for initializing H2 in memory database. It creates login & book
 * tables.
 */
@Controller("/dbinit")
public class DbInitializeCtrl {
    protected final DbInitRepo dir;

    /**
     * Initializer
     * @param repo Initialize DbInit Repository
     */
    public DbInitializeCtrl(final DbInitRepo repo) {
        this.dir = repo;
    }

    /**
     * Function to add Data to tables
     * @return
     */
    @Get("/")
    @Produces(MediaType.TEXT_PLAIN)
    public String addData() {
        /* Implementation */
    }
}

The line of interest here is #13. We are injecting the Repository created earlier in constructor of the method. Also of interest is line #5. This defines the class as a controller and exposes endpoint /dbinit.

Document your code, add Swagger

We are going to take a bottom-up approach to document our services with Swagger. What this means is that we are going to build a documentation for our REST services using swagger – we will also integrate swagger-ui to view this. We intend to generate OpenAPI 3.0 specification documentation. We will have to make three changes to pom.xml as provided below.

<dependencies>
  <dependency>
     <groupId>io.swagger.core.v3</groupId>
     <artifactId>swagger-annotations</artifactId>
     <scope>runtime</scope>
  </dependency>
</dependencies>
::::: :::::
::::: :::::
<build>
  <plugins>
    <plugin>
       <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
          <configuration>
             <fork>true</fork>
             <compilerArgs>
                <arg>-J-Dmicronaut.openapi.views.spec=swagger-ui.enabled=true,swagger-ui.theme=flattop</arg>
                <arg>-parameters</arg>
             </compilerArgs>
             <annotationProcessorPaths>
                <path>
                   <groupId>io.micronaut.configuration</groupId>
                   <artifactId>micronaut-openapi</artifactId>
                   <version>1.3.4</version>
                </path>
             </annotationProcessorPaths>
          </configuration>
       </plugin>
  </plugins>
</build>

Shown above is partial extracts from pom.xml. Following are the lines of interest:

  • #2: Annotations for generating swagger
  • #19: Add support to generate swagger-ui code, we are using flattop theme here
  • #22: Annotation processing support for OpenAPI

In this case we are adding support to generate swagger and swagger-ui. We are also adding support for OpenAPI v3.0. If you use IntelliJ Idea, make sure to enable annotation processing, else you will to be able to compile any code. Swagger and swagger-ui files are generated within META-INF/swagger directory. We will expose an endpoint to access them.

micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**

That’s it from configuration perspective. What remains now is to make sure you have comments in the code. Add OpenAPI comment on Application.java. This is the main class generated by Micronaut. You can find the latest version here (at the time of writing). Given below is the comment from sample code.

@OpenAPIDefinition(
    info = @Info(
        title = "Virtual Library",
        version = "1.0",
        description = "Testing Micronaut Security and Swagger",
        license = @License(name = "Apache 2.0"),
        contact = @Contact(url = "http://suturf-com.ibrave.host/", name = "Suvendra Chakrabarti")
    )
)

You can find the swagger-ui generated API documentation at: http://localhost:<port>/<context>/swagger-ui.

Securing your API

API security is a key concern. We have to ensure about the caller of these APIs. Micronaut provides quite a few ways of securing API. This includes simple basic auth to a more complicated OAuth2. In this discussion we will implement OAuth2 for securing API. If you are looking for more information on OAuth2, refer to the website here.

As always, we start with updating POM to change add this dependency. In this case we are adding the micronaut-security-jwt dependency.

<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-security-jwt</artifactId>
   <scope>compile</scope>
</dependency>
micronaut:
  ssl:                               # <a>
    enabled: true
    buildSelfSigned: true
  security:
    enabled: true                    # <b>
    endpoints:
      login:
        enabled: true                # <c>
      oauth:
        enabled: true                # <d>
    intercept-url-map:               # <e>
      - pattern: /**/swagger/**
        httpMethod: GET
        access:
          - isAnonymous()
      - pattern: /**/swagger-ui/**
        httpMethod: GET
        access:
          - isAnonymous()
    token:
      jwt:
        enabled: true                 # <f>
        signatures:
          secret:
            generator:
              secret:                 # <g>

There is quite a few things going on in this. Let’s go through each of the marked ones.

  • #a: Enables SSL. In this case we let Micronaut generate a SSH certificate
  • #b: Here true indicates that we are enabling security
  • #c: Micronaut will provide an implementation of OAuth2 login endpoint
  • #d: Micronaut will provide an implementation of OAuth2 refresh endpoint
  • #e: Allow swagger anonymous access
  • #f: Generate JWT tokens
  • #g: Add a secret to use for signing messages

Next we will add a class that implements AuthenticationProvider interface. This class will returns user roles and identity. Class below is the concise version for reference.

public class JwtProvider implements AuthenticationProvider {
    @Inject
    LoginRepo lrp;

    @Override
    public Publisher<AuthenticationResponse> authenticate(
            final AuthenticationRequest ar) {

        final String usr = (String)ar.getIdentity();
        final String pwd = (String)ar.getSecret();
        LOG.info("User: {}", usr);

        try {
            if (lrp.validateUser(usr, pwd)) {
                final List<String> roles = Arrays.asList("Administrator");
                return Flowable.just(new UserDetails((String) ar.getIdentity(), roles));
            }
        } catch(SQLException e) {
            LOG.error("JwtProvider::authenticate - Did you run /dbinit?");
        }

        return Flowable.just(new AuthenticationFailed());
    }
}

Now it is just a matter of adding if each controller needs authentication or not. Remember the class I have been deferring till now (DbInitializeCtrl)? We now add an annotation to say this class can be accessed anonymously.

@Secured(SecurityRule.IS_ANONYMOUS)
@Controller("/dbinit")
public class DbInitializeCtrl {

We are defining this controller with IS_ANONYMOUS access. This endpoint is not secure anymore. Let us see how to have the other endpoint secure.

@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller("/books")
public class LibraryCtrl {

Simple! We annotate this class to be Authenticated. We can get the principal name, roles etc within these classes.

Conclusion

That was a long blog entry. However, we covered some very important aspects of microservices. Codes for this entry can be accessed at https://github.com/chakrab/suturf-com.ibrave.host/tree/master/vlibrary. Feel free to explore. Ciao for now.