前言

在 SwaggerUI 中加入登录验证,是我很早前就做过的,不过之前的做法总感觉有点硬编码,最近 .Net8 增加了一个新特性:调用 MapSwagger().RequireAuthorization 来保护 Swagger UI ,但官方的这个功能又像半成品一样,只能使用 postman curl 之类的工具带上 Authorization header 来请求,在浏览器里打开就直接401了 ……

刚好有个项目需要用到这个功能,于是我把之前做过的 SwaggerUI 登录认证中间件拿出来重构了一下。

这次我依然使用 Basic Auth 的方式来登录,写了一个自定义的 SwaggerAuthenticationHandler,通过 Microsoft.AspNetCore.Authentication 提供的扩展方法来实现登录。

PS:本文以我最近在开发的单点认证项目(IdentityServerLite)为例

配置Swagger

这次我试着不按照写代码的顺序,而是站在使用者的角度来介绍,也许会更直观一些。

编辑 src/IdsLite.Api/Extensions/CfgSwagger.cs 文件 (顾名思义,这是用来配置Swagger的相关扩展方法)

public static class CfgSwagger {
public static IServiceCollection AddSwagger(this IServiceCollection services) {
services.AddSwaggerGen();
return services;
} public static IApplicationBuilder UseSwaggerWithAuthorize(this IApplicationBuilder app) {
app.UseMiddleware<SwaggerBasicAuthMiddleware>();
app.UseSwagger();
app.UseSwaggerUI(); return app;
}
}

其他的都是常规的配置,重点在于 app.UseMiddleware<SwaggerBasicAuthMiddleware>(); 添加了一个中间件

SwaggerBasicAuth 中间件

来编写这个中间件,代码路径 src/IdsLite.Api/Middlewares/SwaggerBasicAuthMiddleware.cs

public class SwaggerBasicAuthMiddleware {
private readonly RequestDelegate _next; public SwaggerBasicAuthMiddleware(RequestDelegate next) {
_next = next;
} public async Task InvokeAsync(HttpContext context) {
if (context.Request.Path.StartsWithSegments("/swagger")) {
var result = await context.AuthenticateAsync(AuthSchemes.Swagger);
if (!result.Succeeded) {
context.Response.Headers["WWW-Authenticate"] = "Basic";
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
} await _next(context);
}
}

主要逻辑在 InvokeAsync 方法里

判断当前地址以 /swagger 开头的话,就进入身份认证流程,如果配置了其他 SwaggerUI 地址,记得同步修改这个中间件的配置,或者做成通用的配置,避免硬编码。

这里使用了 Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions 提供的扩展方法 context.AuthenticateAsync("Scheme Name") 来验证身份 (具体的 scheme 我们后面会实现)

如果验证失败的话,返回 401 ,同时添加响应头 WWW-Authenticate:Basic ,这样就能在浏览器里弹出输入用户名和密码的提示框了。

AuthenticationScheme

在注册 Authentication 服务的时候,可以添加一些其他的 scheme

PS: AspNetCore 的这套 Identity 确实有点复杂,用了这么久感觉还是没有系统的认识这个 Identity 框架

注册服务

注册服务的代码大概是这样

services
.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(...)
.AddScheme<AuthenticationSchemeOptions, SwaggerAuthenticationHandler>(AuthSchemes.Swagger, null);

AddScheme 方法可以添加各种类型的认证方案,这里添加了一个自定义的认证方案 SwaggerAuthenticationHandler,后面的参数是方案的名称和选项。

为了避免硬编码,我写了个静态类

public static class AuthSchemes {
public const string Swagger = "SwaggerAuthentication";
}

SwaggerAuthenticationHandler

接下来实现这个自定义的认证方案

其实就是把 Basic Authenticate 和固定用户名和密码结合在一起

不过为了不在代码里硬编码,我把用户名和密码放在配置里了,通过注入 IOption<T> 的方式获取。也可以放在数据库里,通过 EFCore 之类的去读取。

public class SwaggerAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> {
public SwaggerAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) {} public SwaggerAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) {} protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
if (!Request.Headers.TryGetValue("Authorization", out var value)) {
return AuthenticateResult.Fail("Missing Authorization Header");
} var config = Context.RequestServices.GetRequiredService<IOptions<IdsLiteConfig>>().Value; try {
var authHeader = AuthenticationHeaderValue.Parse(value);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(":", 2);
var username = credentials[0];
var password = credentials[1]; if (username != config.Swagger.UserName || password != config.Swagger.Password) {
return AuthenticateResult.Fail("Invalid Username or Password");
} var claims = new[] { new Claim(ClaimTypes.Name, username) };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket);
}
catch {
return AuthenticateResult.Fail("Invalid Authorization Header");
}
}
}

try 里面的代码,就是从 request header 里读取 basic auth 的用户名和密码(通常是 Base64 编码过的),解码之后判断是否正确,然后返回认证结果。

扩展

还可以集成 OpenIDConnect 和 OAuth ,我还没有实践,详情见参考资料。

小结

既要在项目发布后访问 SwaggerUI ,又要保证一定的安全性,本文提供的思路或许是一种比较简单又有效的解决方案。

参考资料

Asp-Net-Core开发笔记:给SwaggerUI加上登录保护功能的更多相关文章

  1. 在CentOS7 开发与部署 asp.net core app笔记

    原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...

  2. 2月送书福利:ASP.NET Core开发实战

    大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...

  3. [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件

    本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...

  4. ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First

    ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库. Entity Frame ...

  5. ASP.NET Core 开发-Logging 使用NLog 写日志文件

    ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以 ...

  6. ASP.NET Core 开发-中间件(StaticFiles)使用

    ASP.NET Core 开发,中间件(StaticFiles)的使用,我们开发一款简易的静态文件服务器. 告别需要使用文件,又需要安装一个web服务器.现在随时随地打开程序即可使用,跨平台,方便快捷 ...

  7. C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式

    C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...

  8. 基于ASP.Net Core开发的一套通用后台框架

    基于ASP.Net Core开发一套通用后台框架 写在前面 这是本人在学习的过程中搭建学习的框架,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 知其然,知其所以然,并非重 ...

  9. ASP.NET Core 开发-中间件(Middleware)

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  10. ASP.NET Core开发-Docker部署运行

    ASP.NET Core开发Docker部署,.NET Core支持Docker 部署运行.我们将ASP.NET Core 部署在Docker 上运行. 大家可能都见识过Docker ,今天我们就详细 ...

随机推荐

  1. Docker学习路线1:介绍

    Docker是什么? Docker是一个开源平台,通过将应用程序隔离到轻量级.可移植的容器中,自动化应用程序的部署.扩展和管理.容器是独立的可执行单元,封装了运行应用程序所需的所有必要依赖项.库和配置 ...

  2. 三七互娱《斗罗大陆:魂师对决》上线,Network Kit助力玩家即刻畅玩

    三七游戏旗下的年度旗舰大作<斗罗大陆:魂师对决>现已开启全平台公测.8月1日,三七互娱技术副总监出席了HMS Core.Sparkle游戏应用创新沙龙,展示了在HMS Core Netwo ...

  3. 国产开源数据库OpenGauss的安装运行

    步骤一:OpenGauss 的安装 环境 OS:openEuler 20.03 64bit with ARM 架构:arm64 部署:单机 安装过程 1.环境配置 安装依赖包: yum install ...

  4. websocket fleck demo

    前言 fleck 比较简洁,想看下他的源码的,先感受一下demo吧. 正文 先上代码. static IDictionary<string, IWebSocketConnection> d ...

  5. node excel采集数据

    前言 个人写过无数的脚本,但是一直没有整理,后续整理脚本. 需求: 生成一堆激活码. 业务: 需要拿到一个token, 然后调用某个api获取激活码. 正文 思路: 1.http请求 axios 2. ...

  6. TypeScript 的理解?与 JavaScript 的区别?

    一.是什么 TypeScript 是 JavaScript 的类型的超集,支持ES6语法,支持面向对象编程的概念,如类.接口.继承.泛型等 ❝ 超集,不得不说另外一个概念,子集,怎么理解这两个呢,举个 ...

  7. Trino418版本动态加载catalog不需要重启集群修改思路及实现2

    原来没事的时候改了一个这样的功能,当时也没有仔细研究,后来也没继续弄.详细可以参考 https://www.cnblogs.com/liuzx8888/p/17635913.html 当时有1个问题: ...

  8. flask通过线程池实现异步

    from flask import Flask from time import sleep from concurrent.futures import ThreadPoolExecutor # D ...

  9. 通过ORPO技术微调 llama3大模型(Fine-tune Llama 3 with ORPO)

    1f45bd1e8577af66a05f5e3fadb0b29 通过ORPO对llama进行微调 前言 ORPO是一种新颖的微调技术,它将传统的监督微调和偏好对齐阶段整合到一个过程中.这减少了训练所需 ...

  10. 如何做好技术 Team Leader?

    简介: 作为一个技术TL(Team Leader),除了自身技能,还会面临诸多团队管理上的困难和挑战.如何定义和明确团队的目标?怎样建立优秀的工程文化?让团队长期发挥战斗力和创新能力的核心是什么?本文 ...