本文档详细说明如何在 Knife4j(基于 SpringDoc OpenAPI 3)中配置全局 Header 参数,使其在 API 文档页面的”Authorize”对话框中显示,实现统一的接口认证和自定义请求头管理。
目录
概述
Knife4j 通过 OpenAPI 3 规范的 Security Scheme 机制来定义全局 Header 参数。配置完成后,这些 Header 会在文档页面的”Authorize”按钮中显示,用户可以统一设置,所有接口测试时会自动携带这些 Header。
核心概念:
- Security Scheme 定义:在
OpenAPI 配置中定义安全方案(如 Bearer Token、API Key 等)
- 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...
|
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(也可以是 QUERY、COOKIE)
- 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")) .components(new Components() .addSecuritySchemes(HttpHeaders.AUTHORIZATION, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT") .description("JWT Bearer Token 认证,请输入 token 值(无需 Bearer 前缀)")) .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 { }
|
方式二:多个 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 { }
|
方式三:方法级别的 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;
@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() .addSecuritySchemes(HttpHeaders.AUTHORIZATION, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT") .description("JWT Bearer Token 认证\n" + "请输入 JWT token 值(无需添加 Bearer 前缀)\n" + "示例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")) .addSecuritySchemes(CustomHeaderConstants.CUSTOM_HEADER, new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) .name(CustomHeaderConstants.CUSTOM_HEADER) .description("自定义请求头,用于标识客户端来源\n" + "示例: client-app-v1"))); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.example.constants;
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;
@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() { return ApiResult.success(exampleService.list()); }
@GetMapping("/{id}") @Operation(summary = "查询示例详情", description = "根据 ID 查询示例数据详情") public ApiResult<ExampleVo> getById(@PathVariable Long id) { return ApiResult.success(exampleService.getById(id)); } }
|
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.*;
@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) { 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.*;
@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"); } }
|
常见问题
可能原因:
- 忘记在 Controller 上添加
@SecurityRequirement 或 @SecurityRequirements 注解
- Security Scheme 的 name 与 Controller 注解中的 name 不匹配(大小写敏感)
- 没有重启应用或清除浏览器缓存
- SpringDoc 依赖版本过低或不兼容
解决方法:
- 确保在
OpenAPI 配置中正确定义了 Security Scheme
- 确保 Controller 上的
@SecurityRequirement(name = "xxx") 中的 name 与 addSecuritySchemes("xxx", ...) 的第一个参数完全一致
- 重启应用并强制刷新浏览器(Ctrl+F5)
- 检查控制台是否有错误日志
- 验证 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
问题描述:
使用 APIKEY 类型定义的 Header,后端无法接收到值或值不正确。
排查步骤:
检查 Header 名称是否一致
1 2 3 4 5
| .name("X-Custom-Header")
String headerValue = request.getHeader("X-Custom-Header");
|
注意:HTTP Header 名称区分大小写!
确认位置设置正确
1
| .in(SecurityScheme.In.HEADER)
|
使用浏览器开发者工具验证
- 打开浏览器开发者工具(F12)
- 切换到 Network 标签
- 执行接口请求
- 查看请求的 Headers 部分,确认
X-Custom-Header 是否存在且值正确
后端日志验证
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
| @RestController @RequestMapping("/api/basic") @Tag(name = "基础接口") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class BasicController { }
@RestController @RequestMapping("/api/advanced") @Tag(name = "高级接口") @SecurityRequirements({ @SecurityRequirement(name = HttpHeaders.AUTHORIZATION), @SecurityRequirement(name = "X-Custom-Header") }) public class AdvancedController { }
@RestController @RequestMapping("/api/public") @Tag(name = "公开接口") public class PublicController { }
@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,如 Authorization、X-API-Key 等
- 在
description 中提供详细的使用说明
- 包含 Token 获取方式、格式要求、示例值等信息
7. 如何处理 Token 过期问题?
问题描述:
在 Knife4j 文档页面测试接口时,Token 可能会过期,导致所有请求返回 401 未授权错误。
解决方案:
重新获取 Token
- 点击 Authorize 按钮
- 清除旧的 Token
- 重新调用登录接口获取新 Token
- 在 Authorize 对话框中填入新 Token
- 点击 Authorize 按钮确认
后端优化建议
1 2 3 4 5 6 7
| @PostMapping("/login") public ApiResult<LoginVo> login(@RequestBody LoginDto dto) { LoginVo vo = authService.login(dto); return ApiResult.success(vo); }
|
前端处理建议
- 实现 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
| @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"))); }
@GetMapping("/data") @Operation(summary = "获取数据") @SecurityRequirements({ @SecurityRequirement(name = "BearerAuth"), @SecurityRequirement(name = "ApiKeyAuth") }) public ApiResult<String> getData() { return ApiResult.success("Data"); }
|
注意:
- 多个
@SecurityRequirement 在 @SecurityRequirements 中是 OR 关系(满足任意一个即可)
- 如果需要同时满足多个认证,需要在后端拦截器中实现自定义逻辑
最佳实践
原因:
- 避免硬编码,减少拼写错误
- 统一管理,便于维护
- 支持 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;
public class ApiHeaderConstants {
public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION;
public static final String CLIENT_ID = "X-Client-Id";
public static final String TRACE_ID = "X-Trace-Id";
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
| .addSecuritySchemes(ApiHeaderConstants.CLIENT_ID, new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) .name(ApiHeaderConstants.CLIENT_ID) .description("客户端标识"))
@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 {
@Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(apiInfo()) .components(securityComponents()); }
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")); }
private Components securityComponents() { return new Components() .addSecuritySchemes(ApiHeaderConstants.AUTHORIZATION, jwtSecurityScheme()) .addSecuritySchemes(ApiHeaderConstants.CLIENT_ID, clientIdSecurityScheme()) .addSecuritySchemes("ApiKeyAuth", apiKeySecurityScheme()); }
private SecurityScheme jwtSecurityScheme() { return new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT") .description("JWT Bearer Token 认证..."); }
private SecurityScheme clientIdSecurityScheme() { return new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) .name(ApiHeaderConstants.CLIENT_ID) .description("客户端标识..."); }
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 { }
@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:配置验证
步骤 2:功能测试
1 2 3 4 5 6 7 8 9 10 11 12
| POST /api/auth/login { "username": "testuser", "password": "testpass" }
|
步骤 3:浏览器调试
步骤 4:后端日志验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @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() .addSecuritySchemes("AuthV1", new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) .name("X-Auth-Token")) .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() + "。当前账号没有访问此接口的权限。"); }
@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
注意: 不同版本的依赖可能在配置方式上有所差异,请参考对应版本的官方文档。
总结
通过本指南,您应该能够:
- ✅ 理解 Knife4j Security Scheme 的工作原理
- ✅ 正确配置 HTTP Bearer Token 和 API Key 类型的 Header
- ✅ 在 Controller 上灵活应用 Security Requirement
- ✅ 解决常见的配置问题和错误
- ✅ 遵循最佳实践,编写可维护的代码
如有任何问题或建议,欢迎反馈交流!