以声明方式在 Swagger UI 中启用授权按钮

问题描述 投票:0回答:1

如何启用 Swagger UI 中的“授权”按钮?我已完成所有设置,但如果端点需要身份验证,则无法直接从 UI 运行。我需要能够将 JWT 令牌作为

Authorization
标头值

传递

我需要传递一些 yml 属性吗?

我应该在这里写点什么吗?


@SpringBootApplication
@OpenAPIDefinition(info = info(version = "1.0", title = "Hello World API"), security = @SecurityRequirement(name = "/* here */"))
public class HelloworldMreApplication {

public static void main(String[] args) {
SpringApplication.run(HelloworldMreApplication.class, args);
}

}

我也尝试过这个:


@RestController
@RequestMapping("/auth")
@Tag(name = "Message Controller")
@SecurityScheme(type = SecuritySchemeType.HTTP, name = "basicAuth")
public class MessageController {

@GetMapping("/hello-world")
// ...

我有按钮,但没用。单击它后,我会看到一个弹出窗口,仅显示“可用授权”

更详细的变化产生相同的结果

@SecurityScheme(type = SecuritySchemeType.HTTP, name = "basicAuth",
description = "authorization with JWT token", scheme = "token",
bearerFormat = "bearer")

这个答案对我来说似乎没有帮助。我确信有一个声明性的解决方案(我更喜欢)。评论中的一个人问是否也可以通过注释来实现,发布答案的人分享了此链接。不过,我在那里没有看到任何基于注释的解决方案

UPD

我发现它与提供给 Swagger UI 的 JSON 完全无关。重要的是服务控制器类上的这个注释:

@RestController
@Tag(name = "Message Controller")
@SecurityScheme(type = SecuritySchemeType.HTTP, name = "basicAuth", scheme = "bearer")
public class MessageController {

该服务的 Swagger UI 现在有此按钮:

但是,我的网关的 UI 仍然有一个功能失调的按钮(请参见上面的第一个屏幕截图)

您可以比较服务和网关的 Swagger UI JSON:它们在所有意图和目的上都是相同的。我检查了他们确实所具有的差异是否可能影响了结果(例如

http
HTTP
)。他们不

仍然很困惑不知道该怎么办。我尝试将

@SecurityScheme(..)
放在网关的主类(用
@SpringBootApplication
注释的类)或随机控制器类上。没有帮助

// service

{
  "openapi": "3.0.1",
  "info": {
    "title": "Hello World API",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "http://localhost:8090",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/joy": {
      "get": {
        "tags": [
          "Message Controller"
        ],
        "operationId": "getMessageOfJoy",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage"
                }
              }
            }
          }
        }
      }
    },
    "/auth/hello-world": {
      "get": {
        "tags": [
          "Message Controller"
        ],
        "operationId": "getHelloWorld",
        "parameters": [
          {
            "name": "principal",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage"
                }
              }
            }
          }
        }
      }
    },
    "/error": {
      "get": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_3",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_2",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_5",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "options": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_6",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "head": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_1",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "my-error-controller"
        ],
        "operationId": "error_4",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SuccessMessage": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "FailureMessage": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "HEAD",
              "POST",
              "PUT",
              "PATCH",
              "DELETE",
              "OPTIONS",
              "TRACE"
            ]
          },
          "request_path": {
            "type": "string"
          }
        }
      }
    },
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  }
}
// gateway

{
  "openapi": "3.0.1",
  "info": {
    "title": "Hello World API",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "https://localhost:8080",
      "description": "Api-Gateway-V2"
    }
  ],
  "paths": {
    "/api/v1/joy": {
      "get": {
        "tags": [
          "Message Controller"
        ],
        "operationId": "getMessageOfJoy",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          }
        }
      }
    },
    "/api/v1/hello-world": {
      "get": {
        "tags": [
          "Message Controller"
        ],
        "operationId": "getHelloWorld",
        "parameters": [
          {
            "name": "principal",
            "in": "query",
            "required": false,
            "style": "FORM",
            "explode": true,
            "schema": {
              "type": "string",
              "exampleSetFlag": false,
              "types": [
                "string"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SuccessMessage": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          }
        },
        "exampleSetFlag": false,
        "types": [
          "object"
        ]
      },
      "FailureMessage": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          },
          "method": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ],
            "enum": [
              "GET",
              "HEAD",
              "POST",
              "PUT",
              "PATCH",
              "DELETE",
              "OPTIONS",
              "TRACE"
            ]
          },
          "request_path": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          }
        },
        "exampleSetFlag": false,
        "types": [
          "object"
        ]
      }
    },
    "securitySchemes": {
      "basicAuth": {
        "type": "HTTP",
        "scheme": "bearer"
      }
    },
    "extensions": {
      
    }
  }
}
java swagger-ui
1个回答
0
投票

终于!我把它固定下来了!我花了一段时间。这是 MRE:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>swagger-ui-mre</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>swagger-ui-mre</name>
    <description>swagger-ui-mre</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.7.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
server:
  port: 8100
springdoc:
  swagger-ui:
    path: /swagger-ui
package com.example.swaggeruimre;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@OpenAPIDefinition(info = @Info(version = "1.0", title = "Hello API"))
public class SwaggerUiMreApplication {

    public static void main(String[] args) {
        SpringApplication.run(SwaggerUiMreApplication.class, args);
    }

}
package com.example.swaggeruimre;

import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Hello Controller")
@RestController
@SecurityScheme(type = SecuritySchemeType.HTTP, name = "bearer-key",
        description = "authorization with JWT token", scheme = "bearer",
        bearerFormat = "JWT")
public class HelloController {
    @GetMapping("/hello")
    public HelloMessage getHello() {
        return new HelloMessage();
    }

    @Getter
    @NoArgsConstructor
    public static class HelloMessage {
        private final String message = "Hello!";
    }
}

2024-02-11_14-11-14

Authorize
按钮有效!

让我们复制 OpenAPI JSON。 这是一个很好的 JSON,可以生成一个按钮

2024-02-11_14-26-53

现在,让我们假设我们从其他地方接收 JSON(就像网关从微服务接收它)。为简单起见,本例中的来源和接收者将是相同的

server:
  port: 8100
springdoc:
  swagger-ui:
    path: /swagger-ui
    url: /open-api
package com.example.swaggeruimre;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OpenApiController {
    @GetMapping("/open-api")
    public String getOpenApi() {
        return """             
                {
                  "openapi": "3.0.1",
                  "info": {
                    "title": "Hello API",
                    "version": "1.0"
                  },
                  "servers": [
                    {
                      "url": "http://localhost:8100",
                      "description": "Generated server url"
                    }
                  ],
                  "paths": {
                    "/hello": {
                      "get": {
                        "tags": [
                          "Hello Controller"
                        ],
                        "operationId": "getHello",
                        "responses": {
                          "200": {
                            "description": "OK",
                            "content": {
                              "*/*": {
                                "schema": {
                                  "$ref": "#/components/schemas/HelloMessage"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  },
                  "components": {
                    "schemas": {
                      "HelloMessage": {
                        "type": "object",
                        "properties": {
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "securitySchemes": {
                      "bearer-key": {
                        "type": "http",
                        "description": "authorization with JWT token",
                        "scheme": "bearer",
                        "bearerFormat": "JWT"
                      }
                    }
                  }
                }
                """;
    }
}

2024-02-11_14-37-46

仍然有效!

现在,让我们更改返回的 JSON,使其成为 bad JSON。我已经知道窍门了。将

"type": "http"
替换为
"type": "HTTP"
。结果:

2024-02-11_14-42-52

现在按钮不起作用了!

您可能想知道,“为什么要将好的 JSON 变成坏的 JSON?”模拟

OpenApiV3Parser
的作用!

假设我返回的不是一个字符串,而是一个对象(例如,我通过调用

openApiV3Parser.readContents(/* json */).getOpenAPI()
获得的):

<!-- you need to add this -->

        <dependency>
            <groupId>io.swagger.parser.v3</groupId>
            <artifactId>swagger-parser</artifactId>
            <version>2.1.18</version>
        </dependency>
package com.example.swaggeruimre;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.OpenAPIV3Parser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OpenApiController {

    @GetMapping("/open-api")
    public OpenAPI getOpenApi() {
        return new OpenAPIV3Parser().readContents("""             
                {
                  "openapi": "3.0.1",
                  "info": {
                    "title": "Hello API",
                    "version": "1.0"
                  },
                  "servers": [
                    {
                      "url": "http://localhost:8100",
                      "description": "Generated server url"
                    }
                  ],
                  "paths": {
                    "/hello": {
                      "get": {
                        "tags": [
                          "Hello Controller"
                        ],
                        "operationId": "getHello",
                        "responses": {
                          "200": {
                            "description": "OK",
                            "content": {
                              "*/*": {
                                "schema": {
                                  "$ref": "#/components/schemas/HelloMessage"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  },
                  "components": {
                    "schemas": {
                      "HelloMessage": {
                        "type": "object",
                        "properties": {
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "securitySchemes": {
                      "bearer-key": {
                        "type": "http",
                        "description": "authorization with JWT token",
                        "scheme": "bearer",
                        "bearerFormat": "JWT"
                      }
                    }
                  }
                }
                """).getOpenAPI();
    }
}

让我们看看 2024-02-11_14-56-33 2024-02-11_15-01-26 2024-02-11_14-57-59

也许我们只需要告诉我们的

ObjectMapper
忽略空值?

package com.example.swaggeruimre;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }
}

2024-02-11_15-06-21 2024-02-11_15-06-47 2024-02-11_15-07-41

不!正是因为一旦我们返回对象而不是字符串,那些嵌套的枚举属性,例如

SecurityScheme.Type
,被序列化为 Swagger UI 无法处理的大写字符串!

考虑到他们覆盖每个枚举的

toString()
以返回小写值

// like so

public class SecurityScheme {
    /**
     * Gets or Sets type
     */
    public enum Type {
        APIKEY("apiKey"),
        HTTP("http"),
        OAUTH2("oauth2"),
        OPENIDCONNECT("openIdConnect"),
        MUTUALTLS("mutualTLS");

        private String value;

        Type(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return String.valueOf(value);
        }
    }

我想他们之前也遇到过这个问题

那么,问题还没有完全解决!

回顾一下:

  1. 如果 Swagger UI 接收到 OpenAPI 对象(而不是字符串),它会以产生大写枚举值的方式对其进行反序列化。这些
    toString()
    在序列化期间不会被调用!
  2. 当底层 OpenAPI 具有大写枚举值时,特别是
    SecurityScheme.Type
    Authorize
    按钮不起作用!

我看到了 Swagger UI 团队的这些方法:

  1. 使 Swagger UI 肚子大写(好)
  2. 告诉 Jackson 将 Open API 枚举序列化为小写字符串(一个拼凑)
// here's one way to do it

public class SecurityScheme {
    /**
     * Gets or Sets type
     */
    public enum Type {
        APIKEY("apiKey"),
        HTTP("http"),
        OAUTH2("oauth2"),
        OPENIDCONNECT("openIdConnect"),
        MUTUALTLS("mutualTLS");

        private String value;

        Type(String value) {
            this.value = value;
        }

        @Override
        @JsonValue
        public String toString() {
            return String.valueOf(value);
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.