本文将介绍如何在 Spring MVC 应用中通过 springdoc-openapi 库配置默认全局安全方案,并将其作为 API 的默认安全要求。同时我们也会探讨如何覆盖这些默认安全要求。OpenAPI 规范允许我们为 API 定义一组安全方案,可以全局配置安全要求,也可以针对每个接口单独应用或移除。

2. 项目搭建

我们使用 Spring Boot 构建 Maven 项目,以下是项目搭建步骤。完成后将得到一个简单的 Web 应用。

2.1. 依赖管理

项目包含两个核心依赖:

  1. spring-boot-starter-web:构建 Web 应用的基础依赖

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
     <version>3.1.5</version>
    </dependency>
    
  2. springdoc-openapi-ui:用于生成 HTML/JSON/YAML 格式的 API 文档

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

2.2. 应用入口

依赖配置完成后,定义应用入口类:

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

3. springdoc-openapi 基础配置

配置好 Spring MVC 后,我们来定义 API 的语义信息。通过在入口类添加 springdoc-openapi 注解来配置全局安全方案和 API 元数据。

使用 @SecurityScheme 注解定义全局安全方案:

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

这里选择了 APIKEY 安全方案类型,实际项目中也可以配置 JWT 等其他方案。接着使用 @OpenAPIDefinition 添加元数据并设置默认安全要求:

@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): 默认全局安全要求

4. 控制器实现

现在添加一个 REST 控制器到上下文根路径。使用 @RestController@RequestMapping 注解:

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

定义两个核心接口:

  1. **/login**:接收用户凭证进行认证,成功后返回令牌
  2. **/ping**:需要 /login 生成的令牌,验证用户权限

4.1. 登录接口实现

此接口不需要安全验证,因此需要覆盖默认安全配置。

首先用 @RequestMapping 定义接口:

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

然后添加语义信息:

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

@SecurityRequirements() 注解显式声明此接口不需要安全验证。状态码 200 的响应文档示例: 登录响应

方法签名实现:

public ResponseEntity login(@Parameter(name = "LoginDto", description = "Login") @Valid @RequestBody(required = true) LoginDto 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 接口文档效果: 登录接口

4.2. Ping 接口实现

此接口使用默认全局安全方案

@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) {
    ...
}

与登录接口的关键区别在于安全要求:

  • 登录接口:无安全要求
  • Ping 接口:继承 API 级别的安全配置

文档中会显示 /ping 接口带锁图标: Ping 接口

5. API 文档访问地址

启动应用服务器:

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

文档访问地址:

这些输出可用于通过 swagger-codegen-maven-plugin 生成多语言客户端/服务端代码。

6. 总结

本文介绍了使用 springdoc-openapi 配置默认全局安全方案的核心技巧:

  1. 通过 @SecurityScheme 定义安全方案
  2. 使用 @OpenAPIDefinition 设置全局安全要求
  3. @SecurityRequirements() 覆盖特定接口的安全配置

此外,我们还发现可以利用 springdoc-openapi 生成的 JSON/YAML 输出自动化代码生成。完整源码可在 GitHub 获取。


原始标题:Apply Default Global SecurityScheme in springdoc-openapi