35

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?

isADon
  • 3,433
  • 11
  • 35
  • 49

6 Answers6

87

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",
            "matyas@justrocket.de");

        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
Matyas
  • 13,473
  • 3
  • 60
  • 73
  • This worked very well. I'm not sure where the `AUTHORIZATION_HEADER` is imported from, but I just hard-coded it and it worked just as well. – Ickster Nov 14 '18 at 03:09
  • 1
    It is a static variable used across Spring Security / etc. ` public static final String AUTHORIZATION_HEADER = "Authorization";` (added to the answer itself) – Matyas Nov 14 '18 at 10:03
  • Hmmm. I wonder if that was deprecated in the version of Spring Security I'm using, or if I just missed it. The only import I recall IntelliJ suggesting was from a different package and the String value wasn't what I was looking for. – Ickster Nov 14 '18 at 19:04
  • I used exact code as above with correct maven dependency, does not show authorize button or does not generate authorization header for the api's – webjockey Aug 06 '20 at 22:24
  • I did exactly as your answer. but, the token is not sent in request headers. can you tell me what am i doing wrong? – Morteza Oct 18 '20 at 13:01
  • this worked with version 3.0.0 as well. The only thing that I changes was to use `Arrays.asList` instead of `Lists.newArrayList` (as I didn't use Guava) and to switch from Swagger2 to OAS (`@EnableOpenApi` and `DocumentationType.OAS_30`) – Adrian B. Nov 06 '20 at 15:25
13

For swagger version 2.9.2

  1. Create a SwaggerConfig class.

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo())
                .securitySchemes(Arrays.asList(apiKey()));
    }
    
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Sig-Predict REST API Document")
                .description("work in progress")
                .termsOfServiceUrl("localhost")
                .version("1.0")
                .build();
    }
    
    private ApiKey apiKey() {
        return new ApiKey("jwtToken", "Authorization", "header");
    }
    
    1. Then annotate each API you would like to send this Authorization header to with:

      @ApiOperation(value = "", authorizations = { @Authorization(value="jwtToken") })
      
Arnaud F.
  • 8,252
  • 11
  • 53
  • 102
moldovean
  • 3,132
  • 33
  • 36
  • 3
    well done, worked for me, thanks! Just to emphasize that in the UI you need to specify the full header "Bearer " – orid May 12 '20 at 23:18
  • @orid I am glad I could help. Yes, the standard is to put 'Bearer' in front, however this config is just for Swagger to work and send to back-end a header with name: "Authorization". The value of this header however is up to the developer. – moldovean May 14 '20 at 15:02
  • @ApiOperation must be added on method level, not to the class. Tried this solution in 3.0.0 version. Thank you) – Orkhan Hasanli Nov 23 '20 at 01:07
4

Your code is correct.

There is a bug in springfox-swagger-ui/springfox-swagger2 version 2.8.0 and it seems 2.9.2 as well. I suspect you are using a version effected by this bug.

I simply downgraded to 2.7.0 and it worked perfectly.

rjdkolb
  • 10,377
  • 11
  • 69
  • 89
  • 2
    I wanna smash my monitor. It is about 4 hours that I am searching for why my code is not working, till I faced your answer and figured out that swagger version was my problem too. thanks man. you saved my day :) – Morteza Oct 18 '20 at 13:08
0

For a quick solution, I configured my docket with a global parameter authorization header in my swaggerConfig class.

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
      private static final Set<String> DEFAULT_PRODUCES_CONSUMES = new HashSet<String>(Arrays.asList("application/json"));
    
      @Bean
      public Docket api() {
        ParameterBuilder parameterBuilder = new ParameterBuilder();
        parameterBuilder.name("Authorization")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .description("JWT token")
                .required(true)
                .build();
        List<Parameter> parameters = new ArrayList<>();
        parameters.add(parameterBuilder.build());
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(DEFAULT_API_INFO)
            .produces(DEFAULT_PRODUCES_CONSUMES)
            .consumes(DEFAULT_PRODUCES_CONSUMES)
            .select()
            .build()
            // Setting globalOperationParameters ensures that authentication header is applied to all APIs
            .globalOperationParameters(parameters);
      }
    }

Wrote a small post authorization-field-in-swagger-ui about this.

ArMD
  • 376
  • 3
  • 13
0

Please try something like below

 return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any()).paths(PathSelectors.regex("/api/v1/.*"))
            .build().groupName("API")
            .globalOperationParameters(newArrayList(
                    new ParameterBuilder().name(HttpHeaders.AUTHORIZATION).description("Authorization token").required(true)
                            .modelRef(new ModelRef("string")).parameterType("header").required(true).build()))
            .apiInfo(apiInfo());
0

Where the accepted answer is correct, it has a small flaw. You have to manually add 'Bearer '-text in the authorization value to make the token work correctly (when the prefix is expected like in my case).

Did some research to improve this and got this working with using the OpenApi without the need for that tiny nasty addition. Source I used to go on with this (Made some minor changes/additions)

In pom.xml I have the following:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.5</version>
    <relativePath />
</parent>
<properties>
    <java.version>16</java.version>
    <swagger.version>2.9.2</swagger.version>
    <open.api.version>1.6.9</open.api.version>
</properties>

<dependencies>
    <!-- Swagger -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>${swagger.version}</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>${swagger.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-ui</artifactId>
        <version>${open.api.version}</version>
    </dependency>
</dependencies>

To the application.properties I added few lines (optional):

spring.mvc.pathmatch.matching-strategy=ant-path-matcher
springdoc.swagger-ui.path=swagger-ui.html
springdoc.paths-to-exclude=/swagger-resources/**

The swagger needed to have some security setting exceptions:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    /* Specify the urls not requiring authentication. */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/webjars/**");
    }
}

And finally the actual configuration for the swagger using OpenApi:

package com.fujitsu.emom.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;

/**
 * Configuration for swagger using OpenApi.<br/>
 * Notice the spring security must allow to access to the swagger ui at 'SecurityConfiguration.java'.<br/>
 * There are also configuration at 'application.properties' for defining the URL to swagger page.
 */
@Configuration
public class SwaggerConfig {

    public static final String SCHEME_NAME = "BearerScheme";
    public static final String SCHEME = "Bearer";

    @Bean
    public OpenAPI customOpenAPI() {
        var openApi = new OpenAPI().info(this.apiInfo());
        this.addSecurity(openApi);
        return openApi;
    }

    private Info apiInfo() {
        var contact = new Contact();
        contact.setEmail("mailbox@product.com");
        contact.setName("product_admin");
        contact.setUrl("http://product.com");
        return new Info()
            .title("Product API")
                .description("Product description")
                .termsOfService("http://product.com/terms_of_service")
                .contact(contact)
                // TODO: Version should be dynamically
                .version("0.5.1");
    }

    private void addSecurity(OpenAPI openApi) {
        var components = this.createComponents();
        var securityItem = new SecurityRequirement().addList(SCHEME_NAME);
        openApi.components(components).addSecurityItem(securityItem);
    }

    private Components createComponents() {
        var components = new Components();
        components.addSecuritySchemes(SCHEME_NAME, this.createSecurityScheme());
        return components;
    }

    private SecurityScheme createSecurityScheme() {
        return new SecurityScheme().name(SCHEME_NAME).type(SecurityScheme.Type.HTTP).scheme(SCHEME);
    }
}
Ville Myrskyneva
  • 1,560
  • 3
  • 20
  • 35