springdoc-openapi 定义全局的默认 SecurityScheme

1、概览

本文将带你了解如何在 Spring MVC Web 应用中使用 springdoc-openapi 配置默认的全局 Security Scheme,并将其应用为 API 的默认安全配置,以及如何覆盖这些默认的安全配置。

OpenAPI 规范 允许为 API 定义一套 Security Scheme。可以配置 API 全局的安全配置,也可以按端点应用/删除安全配置。

2、设置

构建一个 Spring Boot Web 项目,使用 Maven。

2.1、依赖

该示例有两个依赖。第一个是 spring-boot-starter-web,用于构建 Web 应用。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.1</version>
</dependency>

另一个依赖是 springdoc-openapi-ui,它用于输出 HTML、JSON 或 YAML 格式的 API 文档:

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

2.2、启动类

使用 @SpringBootApplication 注解来定义启动类,通过 SpringApplication 类来启动应用:

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

3、springdoc-openapi 基础配置

配置好 Spring MVC 后,来看看 API 语义信息。

通过向 DefaultGlobalSecuritySchemeApplication 类添加 springdoc-openapi 注解来定义默认的全局 Security Scheme 和 API 元数据。

使用 @SecurityScheme 注解定义全局 Security Scheme:

@SecurityScheme(type = SecuritySchemeType.APIKEY, name = "api_key", in = SecuritySchemeIn.HEADER)

这里选择了 APIKEY Security Scheme 类型,也可以配置其他 Security Scheme,例如 JWT。在定义 Security Scheme 之后,使用 @OpenApiDefinition 注解来添加元数据并为 API 建立默认的安全配置(SecurityRequirement)。

@OpenAPIDefinition(info = @Info(title = "Apply Default Global SecurityScheme in springdoc-openapi", version = "1.0.0"), security = { @SecurityRequirement(name = "api_key") })

如上,info 属性定义了 API 元数据。security 属性定义了默认的全局安全配置。

使用了这些注解后的 HTML 文档如下。可以看到元数据以及适用于整个 API 的 “Authorize” 按钮。

默认的全局安全配置

4、Controller

配置好了 springdoc-openapi 后,添加一个 Rest Controller 如下:

@RestController
@RequestMapping("/")
public class DefaultGlobalSecuritySchemeOpenApiController {
    ...
}

然后,定义两个端点或 路径(path)

第一个端点是 /login 端点。它接收用户凭证并对用户进行身份认证。如果验证成功,端点返回一个 Token。

另一个端点是 /ping 端点,需要使用 /login 方法生成的 Token。在执行请求之前,该方法会验证 Token 并检查用户是否获得授权。

总之,/login 端点验证用户身份并提供一个 Token。/ping 端点接收 /login 端点返回的 Token,并检查其是否有效,用户是否可以执行操作。

4.1、login() 方法

该方法没有任何安全配置,需要对默认的安全配置进行覆盖。

首先,添加注解 @RequestMapping 表示这是一个 API 端点:

@RequestMapping(method = RequestMethod.POST, value = "/login", produces = { "application/json" }, consumes = { "application/json" })

之后,需要为端点添加语义信息。使用 @Operation@SecurityRequirements 注解。@Operation 定义端点,@SecurityRequirements 定义适用于端点的特定安全配置集:

@Operation(operationId = "login", responses = {
    @ApiResponse(responseCode = "200", description = "api_key to be used in the secured-ping endpoint", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = TokenDto.class)) }),
    @ApiResponse(responseCode = "401", description = "Unauthorized request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }) })
@SecurityRequirements()

例如,以下是状态代码为 200 响应的 HTML 文档:

状态代码为 200 响应的 HTML 文档

最后,看看 login() 方法的签名:

public ResponseEntity login(@Parameter(name = "LoginDto", description = "Login") @Valid @RequestBody(required = true) LoginDto loginDto) {
    ...
}

如你所见,API 会接收一个 LoginDto 实例。还必须用语义信息装饰 DTO,以便在文档中显示信息:

public class LoginDto {
    private String user;
    private String pass;

    ...

    @Schema(name = "user", required = true)
    public String getUser() {
        return user;
    }

    @Schema(name = "pass", required = true)
    public String getPass() {
        return pass;
    }
}

此时,/login 端点的 HTML 文档如下:

login 端点的 HTML 文档

4.2、ping() 方法

定义 ping() 方法,使用默认的全局 Security Scheme:

@Operation(operationId = "ping", responses = {
    @ApiResponse(responseCode = "200", description = "Ping that needs an api_key attribute in the header", content = {
        @Content(mediaType = "application/json", schema = @Schema(implementation = PingResponseDto.class), examples = { @ExampleObject(value = "{ pong: '2022-06-17T18:30:33.465+02:00' }") }) }),
    @ApiResponse(responseCode = "401", description = "Unauthorized request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }),
    @ApiResponse(responseCode = "403", description = "Forbidden request", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ApplicationExceptionDto.class)) }) })
@RequestMapping(method = RequestMethod.GET, value = "/ping", produces = { "application/json" })
public ResponseEntity ping(@RequestHeader(name = "api_key", required = false) String api_key) {
    ...
}

login()ping() 方法的主要区别在于应用的安全配置。login() 方法根本没有任何安全配置,而 ping() 方法的安全配置则是在 API 层定义的。因此,HTML 文档中的 /ping 端点显示了一把小锁:

Ping 端点

5. REST API 文档 URL

至此,Spring MVC Web 应用已经准备就绪,可以启动服务器了:

mvn spring-boot:run -Dstart-class="com.baeldung.defaultglobalsecurityscheme.DefaultGlobalSecuritySchemeApplication"

服务器启动成功后,可以在 http://localhost:8080/swagger-ui-custom.html URL 上看到 HTML 文档,如前面的示例所示。

JSON 版本的 API 定义在 http://localhost:8080/api-docs 端点,YAML 版本的在 http://localhost:8080/api-docs.yaml 端点。

可以使用 swagger-codegen-maven-plugin 将这些输出用于不同语言的 API 客户端或服务器的构建。

6、总结

本文介绍了如何使用 springdoc-openapi 定义默认全局 Security Scheme,以及如何将其用作 API 的默认安全配置,最后了解了如何更改特定端点的默认安全配置。


参考:https://www.baeldung.com/spring-openapi-global-securityscheme