前言
当需要在生产环境中提供 Swagger UI 时,我们可以通过身份验证,控制只有授权用户才能访问 Swagger UI 页面。
但是我们希望更进一步,每个用户只能看到授权给他的终结点,而不会暴露其他未授权终结点信息。
比如, API 提供了方法 A 和 方法 B,而对于用户 zhangsan 来说,他在 Swagger UI 页面只能看到方法 A 的说明,而不会知道方法 B 的存在。
思路
Swagger UI 页面展示的数据来源,其实是/swagger/v1/swagger.json
文件:
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
而swagger.json
的格式是遵循 OpenAPI 规范的。
其中,paths
定义了 API 的所有终结点:
那么,只需要保证swagger.json
包含的paths
仅定义了授权接口的终结点即可。
虽然我们不能控制swagger.json
文件的生成,但是我们可以控制它如何输出到响应啊!
我们可以定义一个Middleware
,实现如下功能:
判断当前请求是否
swagger.json
如果是,截获原始响应,即
swagger.json
文件内容创建一个新的 json 文件,遍历
swagger.json
所有 JSON 结点,写入到新 json 文件中如果当前节点是
paths
节点下的子节点,先判断子节点名称是否是授权终结点,是则写入,否则跳过将新 json 文件输出到响应中
实现
SwaggerEndpointsFilterMiddleware
实现代码如下:
public class SwaggerEndpointsFilterMiddleware
{private readonly RequestDelegate _next;public SwaggerEndpointsFilterMiddleware(RequestDelegate next){_next = next;}public async Task Invoke(HttpContext httpContext){if (httpContext.Request.Path.Value != null && httpContext.Request.Path.HasValue &&httpContext.Request.Path.Value.Contains("swagger.json", StringComparison.InvariantCultureIgnoreCase)){var originalStream = httpContext.Response.Body;using (var memoryStream = new MemoryStream()){//截获原始响应,将响应内容写入 MemoryStreamhttpContext.Response.Body = memoryStream;await _next(httpContext);//获取当前用户可访问的 Endpointsvar validEndpoints = GetValidEndpoints(httpContext.Request);memoryStream.Position = 0;using (var sr = new StreamReader(memoryStream)){//将修改过的 json 写入响应 await originalStream.WriteAsync(CreateSwaggerJson(sr.ReadToEnd(), validEndpoints));}httpContext.Response.Body = originalStream;return;}}await _next(httpContext);}
}
关键代码在CreateSwaggerJson
:
private byte[] CreateSwaggerJson(string json, IEnumerable<string> validEndpoints)
{using (var memoryStream = new MemoryStream()){using (var utf8JsonWriter = new Utf8JsonWriter(memoryStream)){using (var jsonDocument = JsonDocument.Parse(json)){utf8JsonWriter.WriteStartObject();foreach (var element in jsonDocument.RootElement.EnumerateObject()){if (element.Name == "paths"){utf8JsonWriter.WritePropertyName(element.Name);utf8JsonWriter.WriteStartObject();//遍历 paths 子节点,检查 Endpoint 是否已授权foreach (var endpoint in element.Value.EnumerateObject()){if (validEndpoints.Contains(endpoint.Name)){endpoint.WriteTo(utf8JsonWriter);}}utf8JsonWriter.WriteEndObject();}else{element.WriteTo(utf8JsonWriter);}}utf8JsonWriter.WriteEndObject();}}return memoryStream.ToArray();}
}
最后,修改 Startup.cs:
app.UseMiddleware<SwaggerEndpointsFilterMiddleware>();app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
结论
今天,我们通过 Middleware 拦截swagger.json
的输出,实现了 Swagger UI 仅为用户暴露已授权终结点。
添加微信号【MyIO666】,邀你加入技术交流群