如何启用 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")
这个答案对我来说似乎没有帮助。我确信有一个声明性的解决方案(我更喜欢)。评论中的一个人问是否也可以通过注释来实现,发布答案的人分享了此链接。不过,我在那里没有看到任何基于注释的解决方案
我发现它与提供给 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": {
}
}
}
终于!我把它固定下来了!我花了一段时间。这是 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!";
}
}
Authorize
按钮有效!
让我们复制 OpenAPI JSON。 这是一个很好的 JSON,可以生成一个按钮
现在,让我们假设我们从其他地方接收 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"
}
}
}
}
""";
}
}
仍然有效!
现在,让我们更改返回的 JSON,使其成为 bad JSON。我已经知道窍门了。将
"type": "http"
替换为 "type": "HTTP"
。结果:
现在按钮不起作用了!
您可能想知道,“为什么要将好的 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();
}
}
让我们看看
也许我们只需要告诉我们的
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;
}
}
不!正是因为一旦我们返回对象而不是字符串,那些嵌套的枚举属性,例如
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);
}
}
我想他们之前也遇到过这个问题
那么,问题还没有完全解决!
回顾一下:
toString()
在序列化期间不会被调用!SecurityScheme.Type
,Authorize
按钮不起作用!我看到了 Swagger UI 团队的这些方法:
// 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);
}
}