Spring and Swagger

By Maurizio Farina | Posted on September 2017

This post covers on how automate Swagger JSON API documentation for RESTful web services built with Spring. The documentation is based on the code itself so any changes to our RESTful services will be included automatically.

Why Swagger?

Swagger is a framework to cover API lifecycle from designing to deployment. Swagger includes three tools:

  • Swagger Editor: Designing RESTful webservices
  • Swagger Code Gen: Generating server stubs and client libraries in different languages
  • Swagger UI: Generating interactive documentation

Many REST frameworks allow automatic generation of WADL - Web Application Description Language descriptor but Swagger is comprehensible, human readable and machine readable, easily adjustable.

The toolkit used in the following examples is SpringFox;

The Springfox suite of java libraries are all about automating the generation of machine and human readable specifications for JSON APIs written using the Spring family of projects. Springfox works by examining an application, once, at runtime to infer API semantics based on Spring configurations, class structure and various compile time java Annotations. from Springfox Reference Documentation

Maven dependencies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>

<!-- Swagger UI -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

Enable Swagger configuration

The SwaggerConfiguration class used by ListFeeds project to enable Swagger documentation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.listfeeds.components;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableWebMvc
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return 
            new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build();
    }
}
  • apis: select api according RequestHandler using the following predicates: any, none, withClassAnnotation, withMethodAnnotation and basePackage
  • paths: select api according the path

Add packages to component scan context in Spring application context file:

1
<context:component-scan base-package="com.listfeeds.components;com.listfeeds.controllers" />

The Docket configuration will enable JSON API documentation adding the endopoint v2/api-docs.

Navigate to the http://[host]:[port]/[web-context]/v2/api-docs using the JSON Editor Online tool the result is the following:

JSON API

Adding UI

The Swagger documentation implements HATEOAS so starting from a simple fixed URL (in our case swagger-ui.html) it will be possible to discover all the API exposed from our application.

To enable the UI generation add the following definitions to rest-servlet.xml:

1
2
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />

Swagger CookBook annotations

Annotation Description
@Api Describes the API; main attributes are value and description
@ApiOperation Describes a single API operation
@ApiParam Describes API operation parameters
@ApiModel, @ApiModelProperty Describe the data model and their attributes
@ApiResponses, @ApiResponse Describe all possible responses

For a quick Swagger annotations overview refers to Swagger Documentation.

An example of Swagger annotation used for ListFeeds Health Controller API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.listfeeds.controllers;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping("/health")
@Api(value = "/health", description = "ListFeeds Health RESTful services")
public class HealthController {

    @RequestMapping(value = "/live", method = RequestMethod.GET, produces="application/json")
    @ApiOperation(value = "Simple health check", notes = "It the service is reachable report a simply UP string", response = String.class)
    public String live()  {
        return "\"UP\"";
    }

    @RequestMapping(value = "/info", method = RequestMethod.GET, produces="application/json")
    @ApiOperation(value = "ListFeeds release", notes = "Show the release and other useful information", response = String.class)
    public String info()  {

        String infoService = "\"ListFeeds info REST services release 1.0.0\"";
        return infoService;
    }
}

ListFeeds Health Controller

Adding Security Configuration

This section is useful to test Rest API calls using authentication token over the header.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
package com.listfeeds.components;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 /*To enable support for swagger specification 2.0*/
@EnableWebMvc
public class SwaggerConfiguration {

    private static String AUTHENTICATION_KEY_NAME = "authorization";
    private static String AUTHENTICATION_KEY_VALUE = "@fromSwagger@";

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(getListFeedsMetaData())
                .securitySchemes(new ArrayList<ApiKey>() {
                    {
                        apiKey();
                    }
                }).securityContexts(new ArrayList<SecurityContext>() {
                    {
                        securityContext();
                    }
                });
    }

    private ApiInfo getListFeedsMetaData() {

        return new ApiInfoBuilder()
                .title("ListFeeds")
                .description("ListFeeds API for NLP")
                .version("1.0.0")
                .build();
    }

    /*
     * Define a new API KEY authorization added to header 
     */
    private ApiKey apiKey() {
        return new ApiKey("swaggerApiKey", AUTHENTICATION_KEY_NAME, "header");
    }

    /*
     * Define the security context 
     */
    private SecurityContext securityContext() {

        /*
         * Defining scopes
         */
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;

        List<SecurityReference> securityReferences = new ArrayList<SecurityReference>();
        securityReferences.add(new SecurityReference("swaggerApiKey", authorizationScopes));


        return SecurityContext.builder()
                .securityReferences(securityReferences)
                .forPaths(PathSelectors.regex("/anyPath.*"))
                .build();
    }

    /*
     * Enable Swagger UI form authorization token
     */
    @Bean
    SecurityConfiguration security() {
        return new SecurityConfiguration(
            "test-app-client-id",
            "test-app-client-secret",
            "test-app-realm",
            "test-app",
            AUTHENTICATION_KEY_VALUE,
            ApiKeyVehicle.HEADER, 
            AUTHENTICATION_KEY_NAME, 
            "," /*scope separator*/);
      }
}

The Request Header will be something similar to this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GET /listfeeds-services/v1/health/live HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
authorization: @fromSwagger@
Referer: http://localhost:8080/listfeeds-services/v1/swagger-ui.html
Accept-Encoding: gzip, deflate, br
Accept-Language: it,en-US;q=0.8,en;q=0.6
Cookie: _ga=GA1.1.29653791.1504633199; _gid=GA1.1.1214439274.1506197590

Adding Metadata to UI

Adding Api Info to the Docket in SwaggerConfiguration class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
@EnableSwagger2 /*To enable support for swagger specification 2.0*/
@EnableWebMvc
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(getListFeedsMetaData())
                .securitySchemes(new ArrayList<ApiKey>() {
                    {
                        apiKey();
                    }
                }).securityContexts(new ArrayList<SecurityContext>() {
                    {
                        securityContext();
                    }
                });
    }

    private ApiInfo getListFeedsMetaData() {

        return new ApiInfoBuilder()
                .title("ListFeeds")
                .description("ListFeeds API for NLP")
                .version("1.0.0")
                .build();
    }
...
...
...
}