Search code examples
spring-bootswaggerswagger-uiswagger-2.0

Spring Boot & Swagger UI. Set JWT token


I have a Swagger config like this

@EnableSwagger2
@Configuration
public class SwaggerConfig {
    @Bean
    public Docket api() {
        List<SecurityScheme> schemeList = new ArrayList<>();
        schemeList.add(new ApiKey(HttpHeaders.AUTHORIZATION, "JWT", "header"));
        return new Docket(DocumentationType.SWAGGER_2)
                .produces(Collections.singleton("application/json"))
                .consumes(Collections.singleton("application/json"))
                .ignoredParameterTypes(Authentication.class)
                .securitySchemes(schemeList)
                .useDefaultResponseMessages(false)
                .select()
                .apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot")))
                .paths(PathSelectors.any())
                .build();
    }
}

In the Swagger UI when I click on the Authorize button I enter my JWT token in the value field eyJhbGc..nN84qrBg. Now I expect that any request I do through the Swagger UI will contain the JWT in the header. However, that is not the case. No request contains a Authorization header.

What am I missing?


Solution

  • Original answer

    Support for Authorization: Bearer [JWT_TOKEN] header is working as of version 2.9.2

    Added the following dependencies to build.gradle

    compile("io.springfox:springfox-swagger2:2.9.2") {
        exclude module: 'mapstruct' // necessary in my case to not end up with multiple mapstruct versions
    }
    compile "io.springfox:springfox-bean-validators:2.9.2"
    compile "io.springfox:springfox-swagger-ui:2.9.2"
    

    Configured Swagger via

    @Configuration
    @EnableSwagger2
    @Import(springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration.class)
    public class SwaggerConfiguration {
    
        public static final String AUTHORIZATION_HEADER = "Authorization";
        public static final String DEFAULT_INCLUDE_PATTERN = "/api/.*";
        private final Logger log = LoggerFactory.getLogger(SwaggerConfiguration.class);
    
        @Bean
        public Docket swaggerSpringfoxDocket() {
            log.debug("Starting Swagger");
            Contact contact = new Contact(
                "Matyas Albert-Nagy",
                "https://justrocket.de",
                "[email protected]");
    
            List<VendorExtension> vext = new ArrayList<>();
            ApiInfo apiInfo = new ApiInfo(
                "Backend API",
                "This is the best stuff since sliced bread - API",
                "6.6.6",
                "https://justrocket.de",
                contact,
                "MIT",
                "https://justrocket.de",
                vext);
    
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .pathMapping("/")
                .apiInfo(ApiInfo.DEFAULT)
                .forCodeGeneration(true)
                .genericModelSubstitutes(ResponseEntity.class)
                .ignoredParameterTypes(Pageable.class)
                .ignoredParameterTypes(java.sql.Date.class)
                .directModelSubstitute(java.time.LocalDate.class, java.sql.Date.class)
                .directModelSubstitute(java.time.ZonedDateTime.class, Date.class)
                .directModelSubstitute(java.time.LocalDateTime.class, Date.class)
                .securityContexts(Lists.newArrayList(securityContext()))
                .securitySchemes(Lists.newArrayList(apiKey()))
                .useDefaultResponseMessages(false);
    
            docket = docket.select()
                .paths(regex(DEFAULT_INCLUDE_PATTERN))
                .build();
            watch.stop();
            log.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());
            return docket;
        }
    
    
        private ApiKey apiKey() {
            return new ApiKey("JWT", AUTHORIZATION_HEADER, "header");
        }
    
        private SecurityContext securityContext() {
            return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(DEFAULT_INCLUDE_PATTERN))
                .build();
        }
    
        List<SecurityReference> defaultAuth() {
            AuthorizationScope authorizationScope
                = new AuthorizationScope("global", "accessEverything");
            AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
            authorizationScopes[0] = authorizationScope;
            return Lists.newArrayList(
                new SecurityReference("JWT", authorizationScopes));
        }
    }
    

    Access the ui via http://host:port/<context-root>/swagger-ui.html

    Press Authorize all requests and enter Bearer [JWT_TOKEN]

    Press authorize then enter the Bearer JWT Token

    Voila your next requests will have the JWT header

    enter image description here

    Update 2022-09-24

    After a series of newer projects, I started using springdoc-openapi that generates docs based on javadoc, eliminating the need of extra annotations.

    Writing this for anyone who is willing to give this library a try. I would recommend it/am a happy user of this lib.

    Dependencies

    build.gradle

    [...]
    // swagger ui
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
    implementation 'org.springdoc:springdoc-openapi-javadoc:1.6.9'
    annotationProcessor 'com.github.therapi:therapi-runtime-javadoc-scribe:0.13.0'
    implementation 'com.github.therapi:therapi-runtime-javadoc:0.13.0'
    [...]
    

    Declare Authentication

    Using the project specific SecurityConfiguration.java - define the pattern of the OpenAPI authorization. This case: Bearer in Authorization in the HTTP header.

    import static io.swagger.v3.oas.annotations.enums.SecuritySchemeIn.HEADER;
    import static io.swagger.v3.oas.annotations.enums.SecuritySchemeType.HTTP;
    import io.swagger.v3.oas.annotations.security.SecurityScheme;
    
     @Component
     @SecurityScheme(name = SecurityConfiguration.SECURITY_CONFIG_NAME, in = HEADER, type = HTTP, scheme = "bearer", bearerFormat = "JWT")
     public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
     [...]
         public static final String SECURITY_CONFIG_NAME = "App Bearer token";
     [...]
    

    Usage in REST controllers

    Usage in SomeController.java shall reference the security config

    import static com.x.common.security.SecurityConfiguration.SECURITY_CONFIG_NAME;
    import io.swagger.v3.oas.annotations.security.SecurityRequirement;
    
     @RestController
     @RequestMapping("/api/v1/resources")
     @SecurityRequirement(name = SECURITY_CONFIG_NAME)
     public class ConnectionSyncController {
     
         /**
          * Documentation that will be rendered
          * 
          * supports 
          * 
          * 1. markdown
          * 1. list
          */
         @PostMapping("/{id}/sync")
         @DomainAuthorize(permissionType = BasePermissions.PERM_ADMIN_OPERATIONS)
         public void syncConnection(@PathVariable("id") Long id) {
    

    Configure reachability

    1. Configure location of openapi specs (swagger yml) - default /v3/api-docs
    2. Configure where swagger-ui is located/loads config from
    3. Configure which backends swagger-ui can talk with
    4. In case we are behind a proxy, we need to make sure that the calls are proxied with correct headers for everything to work.

    /src/main/resources/application.yml

     server:
       port: 80
       # needed for swagger-ui to detect correct proxied paths correctly.
       # Configuration needed for the [Try out] buttons to work
       # this works in combination with the proxied headers
       # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       # proxy_set_header X-Forwarded-Prefix /services/impower-facilioo;
       forward-headers-strategy: FRAMEWORK
     
     springdoc:
       swagger-ui:
         # where the UI configuration is located at
         configUrl: /[some/public/path]/v3/api-docs/swagger-config
         filter: true
         deepLinking: true
         # where the server API yml/json files are at (dropdown in top right corner)
         urls[0]:
           url: /[some/public/path]/v3/api-docs
           name: backend