Knife4j 自定义Header认证配置完全指南

本文档详细说明如何在 Knife4j(基于 SpringDoc OpenAPI 3)中配置全局 Header 参数,使其在 API 文档页面的”Authorize”对话框中显示,实现统一的接口认证和自定义请求头管理。

目录


概述

Knife4j 通过 OpenAPI 3 规范的 Security Scheme 机制来定义全局 Header 参数。配置完成后,这些 Header 会在文档页面的”Authorize”按钮中显示,用户可以统一设置,所有接口测试时会自动携带这些 Header。

核心概念:

  1. Security Scheme 定义:在 OpenAPI 配置中定义安全方案(如 Bearer Token、API Key 等)
  2. Security Requirement 应用:在 Controller 类或方法上使用 @SecurityRequirement 注解来声明所需的安全方案

配置流程:

1
OpenAPI配置定义Security Scheme → Controller应用SecurityRequirement → Knife4j自动生成Authorize界面

Security Scheme 类型

1. HTTP Bearer Token(推荐用于 JWT 认证)

1
2
3
4
5
.addSecuritySchemes("Authorization", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证"))

特点:

  • 类型:SecurityScheme.Type.HTTP
  • Scheme:bearer(小写,符合 RFC 6750 规范)
  • Knife4j 会自动在用户输入值前添加 Bearer 前缀
  • 适用于标准的 JWT 认证场景
  • 用户只需输入 token 值,无需手动添加 “Bearer “ 前缀

实际效果:

1
2
用户输入: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
实际Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

2. API Key(用于自定义 Header)

1
2
3
4
5
.addSecuritySchemes("X-Custom-Header", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-Custom-Header")
.description("自定义请求头,用于标识客户端来源"))

特点:

  • 类型:SecurityScheme.Type.APIKEY
  • 位置:SecurityScheme.In.HEADER(也可以是 QUERYCOOKIE)
  • Name:实际的 HTTP Header 名称
  • 用户输入的值会原样传递到 HTTP Header 中,不做任何处理

实际效果:

1
2
用户输入: client-app-v1
实际Header: X-Custom-Header: client-app-v1

类型对比

特性 HTTP Bearer API Key
自动添加前缀 是(自动添加 “Bearer “) 否(原样传递)
适用场景 JWT、OAuth 2.0 Token 自定义 Header、API Key
Header 名称 固定为 Authorization 可自定义任意名称
值处理方式 添加 “Bearer “ 前缀 原样传递

配置步骤

步骤 1:在 Knife4jConfig 中定义 Security Scheme

@Configuration 类中创建 OpenAPI Bean,定义所有需要的 Security Scheme:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class Knife4jConfig {

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("API 文档标题")
.description("API 接口文档")
.version("1.0.0"))
// 添加 Security Scheme 定义
.components(new Components()
// 定义 Bearer Token 认证
.addSecuritySchemes(HttpHeaders.AUTHORIZATION, new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证,请输入 token 值(无需 Bearer 前缀)"))
// 定义自定义 Header
.addSecuritySchemes("X-Custom-Header", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-Custom-Header")
.description("自定义请求头,用于标识客户端来源")));
}
}

重要提示:

  • addSecuritySchemes(name, scheme) 的第一个参数 name 是 Security Scheme 的唯一标识符,在 Controller 中引用时使用
  • 对于 APIKEY 类型,.name() 方法指定的是实际的 HTTP Header 名称
  • 建议使用有意义的 description,帮助 API 使用者理解参数用途

步骤 2:在 Controller 上应用 Security Requirement

方式一:单个 Security Requirement

如果 Controller 只需要一个 Header:

1
2
3
4
5
6
7
@RestController
@RequestMapping("/api/example")
@Tag(name = "示例管理", description = "示例管理相关接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class ExampleController {
// 该 Controller 下所有接口都需要 Authorization Header
}

方式二:多个 Security Requirements

如果 Controller 需要多个 Header:

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/api/example")
@Tag(name = "示例管理", description = "示例管理相关接口")
@SecurityRequirements({
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION),
@SecurityRequirement(name = "X-Custom-Header")
})
public class ExampleController {
// 该 Controller 下所有接口都需要 Authorization 和 X-Custom-Header
}

方式三:方法级别的 Security Requirement

对不同方法应用不同的 Security Requirement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping("/api/public")
@Tag(name = "公开接口")
public class PublicController {

@GetMapping("/health")
@Operation(summary = "健康检查")
public String health() {
// 无需认证
return "OK";
}

@GetMapping("/secure")
@Operation(summary = "需要认证的接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public String secure() {
// 需要认证
return "Secure Data";
}
}

注意事项:

  • @SecurityRequirement(name = "xxx") 中的 name 必须与步骤 1 中 addSecuritySchemes() 的第一个参数完全一致(区分大小写)
  • 使用 @SecurityRequirements 包装多个 @SecurityRequirement
  • 方法级别的注解会覆盖类级别的注解

完整示例

1. 配置类:Knife4jConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.example.config;

import com.example.constants.CustomHeaderConstants;
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.info.License;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;

/**
* Knife4j 配置类
* 配置 API 文档信息和安全认证方案
*/
@Configuration
public class Knife4jConfig {

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("API 文档标题")
.description("API 接口文档")
.version("1.0.0")
.contact(new Contact()
.name("技术支持")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")))
// 添加全局安全配置
.components(new Components()
// Authorization Bearer Token
.addSecuritySchemes(HttpHeaders.AUTHORIZATION, new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证\n" +
"请输入 JWT token 值(无需添加 Bearer 前缀)\n" +
"示例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."))
// Custom Header API Key
.addSecuritySchemes(CustomHeaderConstants.CUSTOM_HEADER, new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(CustomHeaderConstants.CUSTOM_HEADER)
.description("自定义请求头,用于标识客户端来源\n" +
"示例: client-app-v1")));
}
}

2. 常量类:CustomHeaderConstants.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.constants;

/**
* 自定义 Header 常量类
* 统一管理所有自定义请求头名称
*/
public class CustomHeaderConstants {

/**
* 自定义请求头 - 客户端标识
*/
public static final String CUSTOM_HEADER = "X-Custom-Header";

/**
* 私有构造函数,防止实例化
*/
private CustomHeaderConstants() {
throw new AssertionError("常量类不允许实例化");
}
}

3. Controller 示例一:只需要 Authorization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.controller;

import com.example.model.ApiResult;
import com.example.model.ExampleVo;
import com.example.service.ExampleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 示例管理 Controller
* 所有接口都需要 JWT 认证
*/
@RestController
@RequestMapping("/api/example")
@Tag(name = "示例管理", description = "示例数据管理相关接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@RequiredArgsConstructor
public class ExampleController {

private final ExampleService exampleService;

@GetMapping
@Operation(summary = "查询示例列表", description = "分页查询示例数据列表")
public ApiResult<List<ExampleVo>> list() {
// 只需要 Authorization Header
return ApiResult.success(exampleService.list());
}

@GetMapping("/{id}")
@Operation(summary = "查询示例详情", description = "根据 ID 查询示例数据详情")
public ApiResult<ExampleVo> getById(@PathVariable Long id) {
return ApiResult.success(exampleService.getById(id));
}
}

4. Controller 示例二:需要多个 Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.example.controller;

import com.example.constants.CustomHeaderConstants;
import com.example.model.ApiResult;
import com.example.model.ExampleVo;
import com.example.service.ExampleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;

/**
* 高级示例管理 Controller
* 所有接口都需要 JWT 认证和自定义 Header
*/
@RestController
@RequestMapping("/api/advanced-example")
@Tag(name = "高级示例管理", description = "需要额外客户端标识的高级接口")
@SecurityRequirements({
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION),
@SecurityRequirement(name = CustomHeaderConstants.CUSTOM_HEADER)
})
@RequiredArgsConstructor
public class AdvancedExampleController {

private final ExampleService exampleService;

@GetMapping("/{id}")
@Operation(summary = "查询高级示例详情",
description = "需要 JWT 认证和客户端标识的接口")
public ApiResult<ExampleVo> getDetail(@PathVariable Long id) {
// 需要 Authorization 和 X-Custom-Header 两个 Headers
return ApiResult.success(exampleService.getDetail(id));
}

@PostMapping
@Operation(summary = "创建高级示例",
description = "需要 JWT 认证和客户端标识的接口")
public ApiResult<ExampleVo> create(@RequestBody ExampleVo vo) {
return ApiResult.success(exampleService.create(vo));
}
}

5. Controller 示例三:混合认证场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.controller;

import com.example.model.ApiResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;

/**
* 公开接口 Controller
* 部分接口需要认证,部分接口公开访问
*/
@RestController
@RequestMapping("/api/mixed")
@Tag(name = "混合接口", description = "包含公开和需要认证的接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class MixedController {

@GetMapping("/secure")
@Operation(summary = "需要认证的接口", description = "继承类级别的认证要求")
public ApiResult<String> secure() {
// 继承类级别的认证要求
return ApiResult.success("Secure Data");
}

@GetMapping("/public")
@Operation(summary = "公开接口", description = "无需认证即可访问")
@SecurityRequirements({}) // 覆盖类级别的认证要求
public ApiResult<String> publicEndpoint() {
// 无需认证
return ApiResult.success("Public Data");
}
}

常见问题

1. 为什么 Header 没有显示在 Authorize 对话框中?

可能原因:

  • 忘记在 Controller 上添加 @SecurityRequirement@SecurityRequirements 注解
  • Security Scheme 的 name 与 Controller 注解中的 name 不匹配(大小写敏感)
  • 没有重启应用或清除浏览器缓存
  • SpringDoc 依赖版本过低或不兼容

解决方法:

  1. 确保在 OpenAPI 配置中正确定义了 Security Scheme
  2. 确保 Controller 上的 @SecurityRequirement(name = "xxx") 中的 name 与 addSecuritySchemes("xxx", ...) 的第一个参数完全一致
  3. 重启应用并强制刷新浏览器(Ctrl+F5)
  4. 检查控制台是否有错误日志
  5. 验证 SpringDoc 版本是否与 Knife4j 版本兼容

2. Bearer Token 前缀重复问题

问题描述:
使用 HTTP Bearer 类型时,Knife4j 会自动添加 Bearer 前缀。如果用户手动输入 Bearer xxx,会导致最终 Header 为 Authorization: Bearer Bearer xxx,造成认证失败。

正确使用方法:

  • 在 Authorize 对话框中只输入 token 值,不要包含 Bearer 前缀
  • Knife4j 会自动处理:
    1
    2
    用户输入: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    实际Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

错误示例:

1
2
❌ 用户输入: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
实际Header: Authorization: Bearer Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

建议:
在 Security Scheme 的 description 中明确说明用户只需输入 token 值。

3. 如何让某些接口不需要 Security Requirement?

方法 1:方法级别单独添加注解

不在 Controller 类上添加 @SecurityRequirement,而是在需要认证的方法上单独添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping("/api/public")
@Tag(name = "公开接口")
public class PublicController {

@GetMapping("/health")
@Operation(summary = "健康检查")
public ApiResult<String> health() {
// 无需认证
return ApiResult.success("OK");
}

@GetMapping("/secure")
@Operation(summary = "需要认证的接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public ApiResult<String> secure() {
// 需要认证
return ApiResult.success("Secure Data");
}
}

方法 2:使用空的 SecurityRequirements 覆盖

在不需要认证的方法上使用 @SecurityRequirements({})(空数组)来覆盖类级别的要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/api/mixed")
@Tag(name = "混合接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class MixedController {

@GetMapping("/secure")
@Operation(summary = "需要认证的接口")
public ApiResult<String> secure() {
// 继承类级别的认证要求
return ApiResult.success("Secure Data");
}

@GetMapping("/public")
@Operation(summary = "公开接口")
@SecurityRequirements({}) // 覆盖类级别的认证要求
public ApiResult<String> publicEndpoint() {
// 无需认证
return ApiResult.success("Public Data");
}
}

推荐做法:

  • 如果大部分接口需要认证,使用方法 2
  • 如果大部分接口无需认证,使用方法 1

4. APIKEY 类型的 Header 值没有正确传递

问题描述:
使用 APIKEY 类型定义的 Header,后端无法接收到值或值不正确。

排查步骤:

  1. 检查 Header 名称是否一致

    1
    2
    3
    4
    5
    // 配置中的名称
    .name("X-Custom-Header")

    // 后端拦截器期望的名称
    String headerValue = request.getHeader("X-Custom-Header");

    注意:HTTP Header 名称区分大小写!

  2. 确认位置设置正确

    1
    .in(SecurityScheme.In.HEADER)  // 确认是 HEADER 而不是 QUERY 或 COOKIE
  3. 使用浏览器开发者工具验证

    • 打开浏览器开发者工具(F12)
    • 切换到 Network 标签
    • 执行接口请求
    • 查看请求的 Headers 部分,确认 X-Custom-Header 是否存在且值正确
  4. 后端日志验证

    1
    2
    3
    4
    5
    6
    @GetMapping("/test")
    public ApiResult<String> test(HttpServletRequest request) {
    String customHeader = request.getHeader("X-Custom-Header");
    log.info("收到的 X-Custom-Header 值: {}", customHeader);
    return ApiResult.success(customHeader);
    }

5. 多个 Controller 有不同的 Security Requirements 怎么办?

解决方案:
在每个 Controller 上分别配置所需的 @SecurityRequirement@SecurityRequirements

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Controller 1 - 只需要 Authorization
@RestController
@RequestMapping("/api/basic")
@Tag(name = "基础接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class BasicController {
// 只需要 JWT 认证
}

// Controller 2 - 需要多个 Header
@RestController
@RequestMapping("/api/advanced")
@Tag(name = "高级接口")
@SecurityRequirements({
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION),
@SecurityRequirement(name = "X-Custom-Header")
})
public class AdvancedController {
// 需要 JWT 认证 + 自定义 Header
}

// Controller 3 - 不需要认证
@RestController
@RequestMapping("/api/public")
@Tag(name = "公开接口")
public class PublicController {
// 不添加 @SecurityRequirement 注解
// 所有接口都无需认证
}

// Controller 4 - 混合场景
@RestController
@RequestMapping("/api/mixed")
@Tag(name = "混合接口")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class MixedController {

@GetMapping("/secure")
public ApiResult<String> secure() {
// 需要认证
return ApiResult.success("Secure");
}

@GetMapping("/public")
@SecurityRequirements({}) // 覆盖类级别注解
public ApiResult<String> publicApi() {
// 无需认证
return ApiResult.success("Public");
}
}

6. 如何自定义 Authorize 按钮的显示文本?

限制说明:
OpenAPI 3 规范和 Knife4j 的 Authorize 按钮显示文本由 Security Scheme 的 name 参数决定,无法直接自定义显示文本。

替代方案:
通过 .description() 方法提供详细的使用说明,用户点击输入框时会看到描述信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
.addSecuritySchemes("Authorization", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证\n" +
"-------------------------------\n" +
"如何获取 Token:\n" +
"1. 调用登录接口 /api/auth/login\n" +
"2. 从响应中获取 token 字段\n" +
"3. 在此处粘贴 token 值(无需 Bearer 前缀)\n" +
"-------------------------------\n" +
"Token 示例:\n" +
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."))

最佳实践:

  • 使用有意义的 name,如 AuthorizationX-API-Key
  • description 中提供详细的使用说明
  • 包含 Token 获取方式、格式要求、示例值等信息

7. 如何处理 Token 过期问题?

问题描述:
在 Knife4j 文档页面测试接口时,Token 可能会过期,导致所有请求返回 401 未授权错误。

解决方案:

  1. 重新获取 Token

    • 点击 Authorize 按钮
    • 清除旧的 Token
    • 重新调用登录接口获取新 Token
    • 在 Authorize 对话框中填入新 Token
    • 点击 Authorize 按钮确认
  2. 后端优化建议

    1
    2
    3
    4
    5
    6
    7
    // 在响应体中返回 Token 过期时间
    @PostMapping("/login")
    public ApiResult<LoginVo> login(@RequestBody LoginDto dto) {
    LoginVo vo = authService.login(dto);
    // LoginVo 包含 token 和 expiresAt 字段
    return ApiResult.success(vo);
    }
  3. 前端处理建议

    • 实现 Token 自动刷新机制
    • 在 Token 即将过期前主动刷新
    • 拦截 401 响应,提示用户重新登录

8. 同一个接口需要支持多种认证方式怎么办?

场景说明:
某些接口需要支持多种认证方式(如 JWT Token 或 API Key),任意一种认证成功即可访问。

OpenAPI 3 规范支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 在 Knife4jConfig 中定义多个 Security Scheme
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("BearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"))
.addSecuritySchemes("ApiKeyAuth", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-Key")));
}

// 在 Controller 或方法上应用
@GetMapping("/data")
@Operation(summary = "获取数据")
@SecurityRequirements({
@SecurityRequirement(name = "BearerAuth"),
@SecurityRequirement(name = "ApiKeyAuth")
})
public ApiResult<String> getData() {
// 支持 Bearer Token 或 API Key 认证
return ApiResult.success("Data");
}

注意:

  • 多个 @SecurityRequirement@SecurityRequirements 中是 OR 关系(满足任意一个即可)
  • 如果需要同时满足多个认证,需要在后端拦截器中实现自定义逻辑

最佳实践

1. 使用常量管理 Header 名称

原因:

  • 避免硬编码,减少拼写错误
  • 统一管理,便于维护
  • 支持 IDE 重构和查找引用

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.example.constants;

import org.springframework.http.HttpHeaders;

/**
* API Header 常量类
*/
public class ApiHeaderConstants {

/**
* JWT 认证 Header(使用 Spring 提供的常量)
*/
public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION;

/**
* 自定义客户端标识 Header
*/
public static final String CLIENT_ID = "X-Client-Id";

/**
* 自定义请求追踪 ID
*/
public static final String TRACE_ID = "X-Trace-Id";

/**
* API 版本 Header
*/
public static final String API_VERSION = "X-API-Version";

/**
* 私有构造函数,防止实例化
*/
private ApiHeaderConstants() {
throw new AssertionError("常量类不允许实例化");
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在 Knife4jConfig 中使用
.addSecuritySchemes(ApiHeaderConstants.CLIENT_ID, new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(ApiHeaderConstants.CLIENT_ID)
.description("客户端标识"))

// 在 Controller 中使用
@SecurityRequirement(name = ApiHeaderConstants.AUTHORIZATION)
@SecurityRequirement(name = ApiHeaderConstants.CLIENT_ID)
public class SomeController {
// ...
}

// 在后端拦截器中使用
String clientId = request.getHeader(ApiHeaderConstants.CLIENT_ID);

2. 提供清晰详细的描述信息

原则:

  • 说明 Header 的用途
  • 提供格式要求和示例值
  • 包含获取方式(如何获取 Token)
  • 注明注意事项(如是否需要前缀)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.addSecuritySchemes(HttpHeaders.AUTHORIZATION, new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"📌 使用说明:\n" +
"1. 调用登录接口 POST /api/auth/login 获取 Token\n" +
"2. 从响应的 data.token 字段复制 Token 值\n" +
"3. 在下方输入框中粘贴(无需添加 Bearer 前缀)\n" +
"4. 点击 Authorize 按钮完成认证\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"⚠️ 注意事项:\n" +
"- 请勿输入 'Bearer ' 前缀(系统会自动添加)\n" +
"- Token 有效期为 2 小时,过期后需重新获取\n" +
"- 请妥善保管 Token,不要泄露给他人\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"✅ Token 示例:\n" +
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"))

3. 统一管理 Security Scheme

建议:
将所有 Security Scheme 定义集中在一个配置类中,便于维护和管理。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Configuration
public class Knife4jConfig {

/**
* 自定义 OpenAPI 配置
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(apiInfo())
.components(securityComponents());
}

/**
* API 基本信息
*/
private Info apiInfo() {
return new Info()
.title("系统 API 文档")
.description("RESTful API 接口文档")
.version("1.0.0")
.contact(new Contact()
.name("技术支持")
.email("support@example.com")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html"));
}

/**
* 安全组件配置
* 集中管理所有 Security Scheme
*/
private Components securityComponents() {
return new Components()
// JWT Bearer Token 认证
.addSecuritySchemes(ApiHeaderConstants.AUTHORIZATION, jwtSecurityScheme())
// 客户端标识
.addSecuritySchemes(ApiHeaderConstants.CLIENT_ID, clientIdSecurityScheme())
// API Key 认证
.addSecuritySchemes("ApiKeyAuth", apiKeySecurityScheme());
}

/**
* JWT Bearer Token Security Scheme
*/
private SecurityScheme jwtSecurityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Bearer Token 认证...");
}

/**
* 客户端标识 Security Scheme
*/
private SecurityScheme clientIdSecurityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(ApiHeaderConstants.CLIENT_ID)
.description("客户端标识...");
}

/**
* API Key Security Scheme
*/
private SecurityScheme apiKeySecurityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-Key")
.description("API Key 认证...");
}
}

4. Controller 模块化分组

原则:
按功能模块将 Controller 分组,相同 Security Requirements 的 Controller 放在同一个 package 中。

目录结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
src/main/java/com/example/controller/
├── public/ # 公开接口(无需认证)
│ ├── HealthController.java
│ └── PublicInfoController.java
├── auth/ # 认证相关接口(只需要基础认证)
│ ├── LoginController.java
│ └── RegisterController.java
├── user/ # 用户管理接口(需要 JWT 认证)
│ ├── UserController.java
│ └── UserProfileController.java
└── admin/ # 管理后台接口(需要 JWT + 特殊权限)
├── AdminUserController.java
└── AdminSystemController.java

分组注解示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 公开接口基类
@RestController
@Tag(name = "公开接口")
public abstract class PublicBaseController {
// 不添加 @SecurityRequirement
}

// 需要认证的接口基类
@RestController
@SecurityRequirement(name = ApiHeaderConstants.AUTHORIZATION)
public abstract class SecureBaseController {
// 所有子类自动继承认证要求
}

// 管理后台接口基类
@RestController
@SecurityRequirements({
@SecurityRequirement(name = ApiHeaderConstants.AUTHORIZATION),
@SecurityRequirement(name = ApiHeaderConstants.ADMIN_TOKEN)
})
public abstract class AdminBaseController {
// 所有子类自动继承多重认证要求
}

5. 完整的测试验证流程

步骤 1:配置验证

1
2
3
4
5
// 启动应用后访问
http://localhost:8080/doc.html

// 检查 Authorize 按钮是否显示
// 点击 Authorize 按钮,检查是否显示所有配置的 Security Scheme

步骤 2:功能测试

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 调用登录接口获取 Token
POST /api/auth/login
{
"username": "testuser",
"password": "testpass"
}

// 2. 复制响应中的 Token

// 3. 点击 Authorize 按钮,填入 Token

// 4. 测试需要认证的接口,确认请求成功

步骤 3:浏览器调试

1
2
3
4
5
// 打开浏览器开发者工具(F12)
// 切换到 Network 标签
// 执行接口请求
// 检查请求 Headers,确认包含:
// Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

步骤 4:后端日志验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在拦截器或 Controller 中添加日志
@Component
public class AuthInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
log.info("收到请求,Authorization Header: {}", token);

String clientId = request.getHeader(ApiHeaderConstants.CLIENT_ID);
log.info("收到请求,Client-ID Header: {}", clientId);

return true;
}
}

6. 版本控制和向下兼容

场景:
API 升级时可能需要修改 Security Scheme,如何保证向下兼容?

方案一:版本化 Security Scheme

1
2
3
4
5
6
7
8
9
10
11
.components(new Components()
// V1 版本 - 旧版本 API 使用
.addSecuritySchemes("AuthV1", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-Auth-Token"))
// V2 版本 - 新版本 API 使用
.addSecuritySchemes("AuthV2", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))

方案二:支持多种认证方式

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/api/v2/users")
@SecurityRequirements({
@SecurityRequirement(name = "AuthV1"), // 兼容旧版本
@SecurityRequirement(name = "AuthV2") // 推荐新版本
})
public class UserController {
// 后端拦截器同时支持两种认证方式
}

7. 错误处理和友好提示

实现全局异常处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 处理认证失败异常
*/
@ExceptionHandler(AuthenticationException.class)
public ApiResult<Void> handleAuthenticationException(AuthenticationException e) {
return ApiResult.error(401, "认证失败: " + e.getMessage() +
"。请检查 Token 是否正确或已过期," +
"可在 Knife4j 文档页面点击 Authorize 按钮重新设置。");
}

/**
* 处理授权失败异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ApiResult<Void> handleAccessDeniedException(AccessDeniedException e) {
return ApiResult.error(403, "权限不足: " + e.getMessage() +
"。当前账号没有访问此接口的权限。");
}

/**
* 处理缺少必需 Header 异常
*/
@ExceptionHandler(MissingRequestHeaderException.class)
public ApiResult<Void> handleMissingHeader(MissingRequestHeaderException e) {
return ApiResult.error(400, "缺少必需的请求头: " + e.getHeaderName() +
"。请在 Knife4j 文档页面点击 Authorize 按钮设置。");
}
}

参考资料

官方文档

相关技术

学习资源

版本兼容性

本文档基于以下版本编写和测试:

  • Spring Boot: 2.7.x / 3.x
  • SpringDoc OpenAPI: 1.7.x / 2.x
  • Knife4j: 4.x
  • Java: 8 / 11 / 17

注意: 不同版本的依赖可能在配置方式上有所差异,请参考对应版本的官方文档。


总结

通过本指南,您应该能够:

  1. ✅ 理解 Knife4j Security Scheme 的工作原理
  2. ✅ 正确配置 HTTP Bearer Token 和 API Key 类型的 Header
  3. ✅ 在 Controller 上灵活应用 Security Requirement
  4. ✅ 解决常见的配置问题和错误
  5. ✅ 遵循最佳实践,编写可维护的代码

如有任何问题或建议,欢迎反馈交流!