Asp-Net-Core开发笔记:给SwaggerUI加上登录保护功能
前言
在 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 ,又要保证一定的安全性,本文提供的思路或许是一种比较简单又有效的解决方案。
参考资料
- https://medium.com/@niteshsinghal85/securing-swagger-in-production-92d0a045a5
- https://medium.com/@niteshsinghal85/securing-swagger-ui-in-production-in-asp-net-core-part-2-dc2ae0f03c73
Asp-Net-Core开发笔记:给SwaggerUI加上登录保护功能的更多相关文章
- 在CentOS7 开发与部署 asp.net core app笔记
原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...
- 2月送书福利:ASP.NET Core开发实战
大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...
- [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件
本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...
- 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 ...
- ASP.NET Core 开发-Logging 使用NLog 写日志文件
ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以 ...
- ASP.NET Core 开发-中间件(StaticFiles)使用
ASP.NET Core 开发,中间件(StaticFiles)的使用,我们开发一款简易的静态文件服务器. 告别需要使用文件,又需要安装一个web服务器.现在随时随地打开程序即可使用,跨平台,方便快捷 ...
- 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 ...
- 基于ASP.Net Core开发的一套通用后台框架
基于ASP.Net Core开发一套通用后台框架 写在前面 这是本人在学习的过程中搭建学习的框架,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 知其然,知其所以然,并非重 ...
- ASP.NET Core 开发-中间件(Middleware)
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
- ASP.NET Core开发-Docker部署运行
ASP.NET Core开发Docker部署,.NET Core支持Docker 部署运行.我们将ASP.NET Core 部署在Docker 上运行. 大家可能都见识过Docker ,今天我们就详细 ...
随机推荐
- C#只允许启动一个WinFrom进程
[STAThread] public static void Main() { bool ret; System.Threading.Mutex mutex = new System.Thread ...
- 优先队列的基本实现【数据结构与算法—TypeScript 实现】
笔记整理自 coderwhy 『TypeScript 高阶数据结构与算法』课程 特性 效率比普通队列高 每个出队元素拥有最高优先级 可以用 数组.链表 等数据结构实现,但是 堆结构 是最常用的实现方式 ...
- 面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这
写在开头 在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器),而CountDownLatch又是其中典型的代表,我们今天就继续来学一下这 ...
- Centos调整分区存储大小
将/home下900G转移到/目录下 1.查看分区大小:df -hl 2.备份home文件:tar cvf /run/home.tar /home 3.终止home文件进程(切换到非home路径下执行 ...
- nginx重新整理——————静态服务[四]
前言 简单介绍一下nginx的静态服务. 正文 一般静态服务一般是alias 和 root. 就是上面这个哈. 那么root和alias 的区别是啥呢? 比如root: 然后修改一下就是: 如果这样配 ...
- websocket fleck demo
前言 fleck 比较简洁,想看下他的源码的,先感受一下demo吧. 正文 先上代码. static IDictionary<string, IWebSocketConnection> d ...
- MMDeploy部署实战系列【第六章】:将编译好的MMdeploy导入到自己的项目中 (C++)
MMDeploy部署实战系列[第六章]:将编译好的MMdeploy导入到自己的项目中 (C++) 这个系列是一个随笔,是我走过的一些路,有些地方可能不太完善.如果有那个地方没看懂,评论区问就可以,我给 ...
- 【cef编译包】下载地址
http://opensource.spotify.com/cefbuilds/index.html
- BURP保存多个监听器配置
"感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 前言 在进 ...
- 学习 Avalonia 框架笔记 如何创建一个全屏置顶的 X11 应用窗口
本文记录我从 Avalonia 框架里面学到如何创建一个全屏置顶的 X11 应用窗口的方法 开始之前,先从 Avalonia 或 CPF 里面拷贝足够的代码,这部分代码可以从本文末尾找到下载方法 设置 ...