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 ,今天我们就详细 ...
随机推荐
- Docker学习路线1:介绍
Docker是什么? Docker是一个开源平台,通过将应用程序隔离到轻量级.可移植的容器中,自动化应用程序的部署.扩展和管理.容器是独立的可执行单元,封装了运行应用程序所需的所有必要依赖项.库和配置 ...
- 三七互娱《斗罗大陆:魂师对决》上线,Network Kit助力玩家即刻畅玩
三七游戏旗下的年度旗舰大作<斗罗大陆:魂师对决>现已开启全平台公测.8月1日,三七互娱技术副总监出席了HMS Core.Sparkle游戏应用创新沙龙,展示了在HMS Core Netwo ...
- 国产开源数据库OpenGauss的安装运行
步骤一:OpenGauss 的安装 环境 OS:openEuler 20.03 64bit with ARM 架构:arm64 部署:单机 安装过程 1.环境配置 安装依赖包: yum install ...
- websocket fleck demo
前言 fleck 比较简洁,想看下他的源码的,先感受一下demo吧. 正文 先上代码. static IDictionary<string, IWebSocketConnection> d ...
- node excel采集数据
前言 个人写过无数的脚本,但是一直没有整理,后续整理脚本. 需求: 生成一堆激活码. 业务: 需要拿到一个token, 然后调用某个api获取激活码. 正文 思路: 1.http请求 axios 2. ...
- TypeScript 的理解?与 JavaScript 的区别?
一.是什么 TypeScript 是 JavaScript 的类型的超集,支持ES6语法,支持面向对象编程的概念,如类.接口.继承.泛型等 ❝ 超集,不得不说另外一个概念,子集,怎么理解这两个呢,举个 ...
- Trino418版本动态加载catalog不需要重启集群修改思路及实现2
原来没事的时候改了一个这样的功能,当时也没有仔细研究,后来也没继续弄.详细可以参考 https://www.cnblogs.com/liuzx8888/p/17635913.html 当时有1个问题: ...
- flask通过线程池实现异步
from flask import Flask from time import sleep from concurrent.futures import ThreadPoolExecutor # D ...
- 通过ORPO技术微调 llama3大模型(Fine-tune Llama 3 with ORPO)
1f45bd1e8577af66a05f5e3fadb0b29 通过ORPO对llama进行微调 前言 ORPO是一种新颖的微调技术,它将传统的监督微调和偏好对齐阶段整合到一个过程中.这减少了训练所需 ...
- 如何做好技术 Team Leader?
简介: 作为一个技术TL(Team Leader),除了自身技能,还会面临诸多团队管理上的困难和挑战.如何定义和明确团队的目标?怎样建立优秀的工程文化?让团队长期发挥战斗力和创新能力的核心是什么?本文 ...