isADon
isADon

Reputation: 3673

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?

Upvotes: 37

Views: 77631

Answers (7)

Devil
Devil

Reputation: 111

Ref - Spring Boot 3 + JWT + Swagger Example
To ensure that the JWT token is included in the Authorization header for requests made through the Swagger UI, you need to configure the securityContexts and securityDefinitions properly in your Swagger configuration.
To implement swagger for JWT token for Spring Boot 3, had to follow the below steps -

  1. Add swagger dependency-

    <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.0.3</version>
    </dependency>

  2. Next we create a class named SwaggerConfig that uses the Spring Framework's @Configuration annotation to define a bean for generating Swagger documentation. We create an OpenAPI object with information about the authentication service, including the title, description. Most importantly in this config we create a security scheme for bearer authentication, specifying the scheme name, type, and bearer format. @Bean public OpenAPI customOpenAPI() {

     return new OpenAPI()
             .info(new Info().title("JavaInUse Authentication Service"))             
             .addSecurityItem(new SecurityRequirement().addList("JavaInUseSecurityScheme"))
             .components(new Components().addSecuritySchemes("JavaInUseSecurityScheme", new SecurityScheme()
                     .name("JavaInUseSecurityScheme").type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")));
    

    } enter image description here

Upvotes: 0

Matyas
Matyas

Reputation: 13712

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

Upvotes: 88

Ville Myrskyneva
Ville Myrskyneva

Reputation: 1640

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("[email protected]");
        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);
    }
}

Upvotes: -1

Mateusz Owsiański
Mateusz Owsiański

Reputation: 9

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());

Upvotes: -1

ArMD
ArMD

Reputation: 404

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.

Upvotes: -1

moldovean
moldovean

Reputation: 3271

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") })
      

Upvotes: 13

rjdkolb
rjdkolb

Reputation: 11888

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.

Upvotes: 3

Related Questions