Using Micronaut Annotation Mapping to Automatically Add Security Info to Swagger Docs
Tags: micronaut, swagger, java
Published: 2020-06-26 16:46:06

---
title: Using Micronaut Annotation Mapping to Automatically Add Security Info to Swagger Docs
published: true
date: 2020-06-26 16:41:34 UTC
tags: Micronaut, Swagger, Java
canonical_url: https://blog.wick.technology/micronaut/annotation-mapping-openapi/
---

In my [last post](https://dev.to/philhardwick/custom-micronaut-security-rules-616) I introduced how to create a custom security rule in [Mirconaut](https://micronaut.io) which allows you to secure an endpoint by requiring a particular permission for a resource ID. In [Mettle](https://mettle.co.uk), we wanted to automatically add some documentation to [Swagger](https://swagger.io) based on the custom security annotation so any person consuming the API documentation would know what permissions are necessary. This is a good example of documentation generated from code, so that it is always correct and up to date.

The [OpenAPI Specification](https://swagger.io/specification/) allows you to add extra information via “Specification Extensions”. In Java annotations these are specified like so:

```
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;

@Operation(
            extensions = {
                    @Extension(name = "extensionName",
                    properties = {
                            @ExtensionProperty(name = "name", value = "value")
                    })
            }
    )
```

To add these annotations automatically however, my colleague Nejc and I, used an annotation mapper to hook into the Micronaut compilation process. This is how we did it:

## Dependencies

Start a new gradle project and add to your build.gradle:

```
implementation platform("io.micronaut:micronaut-bom:$micronautVersion")
implementation "io.micronaut:micronaut-inject"
implementation "io.micronaut:micronaut-runtime"
implementation("io.swagger.core.v3:swagger-annotations") 
implementation project(":custom-security-rule-lib")
```

This will add the necessary Micronaut and Swagger annotations to your classpath.

## Annotation Mapper

Extend the `TypedAnnotationMapper<RequiredPermission>` interface and implement like:

```
import custom.security.rule.RequiredPermission;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.inject.annotation.TypedAnnotationMapper;
import io.micronaut.inject.visitor.VisitorContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;

import java.util.Collections;
import java.util.List;

public class OpenApiAnnotationMapper implements TypedAnnotationMapper<RequiredPermission> {
    @Override
    public Class<RequiredPermission> annotationType() {
        return RequiredPermission.class;
    }

    @Override
    public List<AnnotationValue<?>> map(AnnotationValue<RequiredPermission> annotation, 
            VisitorContext visitorContext) {
        String resourceIdName = annotation.stringValue("resourceIdName").orElse(null);
        String permission = annotation.stringValue("permission").orElse(null);

        AnnotationValue<ExtensionProperty> extensionProp = AnnotationValue.builder(ExtensionProperty.class)
                .member("name", "AuthorisationDescription")
                .member("value", "Your JWT needs to have " + permission + " permissions for " + resourceIdName)
                .build();

        AnnotationValue<Extension> extension = AnnotationValue.builder(Extension.class)
                .member("name", "Security")
                .member("properties", new AnnotationValue[]{extensionProp})
                .build();

        AnnotationValue<Operation> operation = AnnotationValue.builder(Operation.class)
                .member("extensions", new AnnotationValue[]{extension})
                .build();
        return List.of(operation);
    }
}
```

Here I’ve decided to use `TypedAnnotationMapper` and have the jar which provides the custom security rule annotation on the annotation processor classpath, but it’s also possible to use `NamedAnnotationMapper`. `NamedAnnotationMapper` allows you to map the annotation without having it on the classpath - this is useful if you want to keep you annotation processor classpath small, as suggested in the [Micronaut docs](https://docs.micronaut.io/latest/guide/index.html#annotationMetadata).

Implementing the mapping is a case of:

1. getting the information from the custom security rule
2. creating an extension property 
3. creating an extension which holds a list of that extension property
4. creating an operation which holds a list of that extension
5. return that operation

Micronaut won’t remove the annotation you’re mapping, so your endpoint will still be secured by the security rule. It also won’t remove any existing `Operation` annotations, it will merge the new `Operation` annotation with the existing one.

## Making the mapper discoverable

To make the mapper discoverable via the java service loader create a file in the resources folder called `META-INF/services/io.micronaut.inject.annotation.AnnotationMapper` and put the fully qualified name of the mapper inside:

```
custom.security.rule.openapi.OpenApiAnnotationMapper
```

## Using your mapper in another project

Add the jar as an annotation processor, for example, in gradle add the following to your dependencies:

```
//to generate the openapi docsc
annotationProcessor("io.micronaut.configuration:micronaut-openapi:1.5.1")
//This shows using the jar from a multi-module project 
//if you're uploading this to a maven repo use the artefact coordinates
annotationProcessor project(":custom-security-rule-openapi") 
```

## What gets generated

When the above class and service descriptor is added to the annotation processor classpath, the annotation mapper will run before the Micronaut openapi annotation processor. This means the OpenAPI processor will parse the mapped `@Extension` annotation and generate the following:

```
openapi: 3.0.1
info:
  title: Example API
  version: v1
paths:
  /tenant/{tenantId}:
    get:
      operationId: index
      parameters:
      - name: tenantId
        in: path
        required: true
        schema:
          type: string
      responses:
        default:
          description: index default response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpStatus'
      x-Security:
        AuthorisationDescription: Your JWT needs to have READ_ONLY permissions for tenantId
```

Notice the `x-Security` key which holds the info specified in our AnnotationMapper.

## Conclusion

Micronaut’s compilation process is really powerful and allows a lot of custom functionality to be created - this is just scratching the surface. Another way to do the above is to use a `TypeElementVisitor` to modify the `@Operation` annotation when a `@RequiredPermission` is present. I’ll aim put a blog post up about that to follow this one.

The code from above, including unit tests, is all in a public github repo: [https://github.com/PhilHardwick/micronaut-custom-security-rule](https://github.com/PhilHardwick/micronaut-custom-security-rule).