在asp.net core2.1中添加中间件以扩展Swashbuckle.AspNetCore3.0支持简单的文档访问权限控制

Swashbuckle.AspNetCore3.0 介绍

一个使用 ASP.NET Core 构建的 API 的 Swagger 工具。直接从您的路由,控制器和模型生成漂亮的 API 文档,包括用于探索和测试操作的 UI。 项目主页:https://github.com/domaindrivendev/Swashbuckle.AspNetCore 划重点,使用多看看 Readme,然后看下项目官方示例,遇到问题找找 issues 继上篇Swashbuckle.AspNetCore3.0 的二次封装与使用分享了二次封装的代码,本篇将分享如何给文档添加一个登录页,控制文档的访问权限(文末附完整 Demo)

关于生产环境接口文档的显示

在此之前的接口项目中,若使用了 Swashbuckle.AspNetCore,都是控制其只在开发环境使用,不会就这样将其发布到生产环境(安全第一) 。 那么,怎么安全的发布 swagger 呢?我有两种想法

  • 将路由前缀改得超级复杂
  • 添加一个拦截器控制 swagger 文档的访问必须获得授权(登录)

大佬若有更好的想法,还望指点一二

下面我将介绍基于 asp.net core2.1 且使用了 Swashbuckle.AspNetCore3.0 的项目种是怎么去实现安全校验的 通过本篇文章之后,可以放心的将项目中的 swagger 文档发布到生产环境,并使其可通过用户名密码去登录访问,得以安全且方便的测试接口。

实现思路

前面已经说到,需要一个拦截器,而这个拦截器还需要是全局的,在 asp.net core 中,自然就需要用到的是中间件

步骤如下,在 UseSwagger 之前使用自定义的中间件 拦截所有 swagger 相关请求,判断是否授权登录 若未登录则跳转到授权登录页,登录后即可访问 swagger 的资源

如果项目本身有登录系统,可在自定义中间件中使用项目中的登录, 没有的话,我会分享一个简单的用户密码登录的方案

Demo 如下图所示

为使用 Swashbuckle.AspNetCore3 的项目添加接口文档登录功能

在写此功能之前,已经封装了一部分代码,此功能算是在此之前的代码封装的一部分,不过是后面完成的。文中代码删除了耦合,和 demo 中会有一点差异。

定义模型存放用户密码

    public class CustomSwaggerAuth
    {
        public CustomSwaggerAuth() { }
        public CustomSwaggerAuth(string userName,string userPwd)
        {
            UserName = userName;
            UserPwd = userPwd;
        }
        public string UserName { get; set; }
        public string UserPwd { get; set; }
        //加密字符串
        public string AuthStr
        {
            get
            {
                return SecurityHelper.HMACSHA256(UserName + UserPwd);
            }
        }
    }

加密方法(HMACSHA256)

    public static string HMACSHA256(string srcString, string key="abc123")
    {
        byte[] secrectKey = Encoding.UTF8.GetBytes(key);
        using (HMACSHA256 hmac = new HMACSHA256(secrectKey))
        {
            hmac.Initialize();

            byte[] bytes_hmac_in = Encoding.UTF8.GetBytes(srcString);
            byte[] bytes_hamc_out = hmac.ComputeHash(bytes_hmac_in);

            string str_hamc_out = BitConverter.ToString(bytes_hamc_out);
            str_hamc_out = str_hamc_out.Replace("-", "");

            return str_hamc_out;
        }
    }

自定义中间件

此中间件中有使用的 login.html,其属性均为内嵌资源,故事用 GetManifestResourceStream 读取文件流并输出,这样可以方便的将其进行封装到独立的类库中,而不与输出项目耦合 关于退出按钮,可以参考前文自定义 index.html

    private const string SWAGGER_ATUH_COOKIE = nameof(SWAGGER_ATUH_COOKIE);
    public void Configure(IApplicationBuilder app)
    {
        var options=new {
            RoutePrefix="swagger",
            SwaggerAuthList = new List<CustomSwaggerAuth>()
            {
                new CustomSwaggerAuth("swaggerloginer","123456")
            },
        }
        var currentAssembly = typeof(CustomSwaggerAuth).GetTypeInfo().Assembly;
        app.Use(async (context, next) =>
        {
            var _method = context.Request.Method.ToLower();
            var _path = context.Request.Path.Value;
            // 非swagger相关请求直接跳过
            if (_path.IndexOf($"/{options.RoutePrefix}") != 0)
            {
                await next();
                return;
            }
            else if (_path == $"/{options.RoutePrefix}/login.html")
            {
                //登录
                if (_method == "get")
                {
                    //读取CustomSwaggerAuth所在程序集内嵌的login.html并输出
                    var stream = currentAssembly.GetManifestResourceStream($"{currentAssembly.GetName().Name}.login.html");
                    byte[] buffer = new byte[stream.Length];
                    stream.Read(buffer, 0, buffer.Length);
                    context.Response.ContentType = "text/html;charset=utf-8";
                    context.Response.StatusCode = StatusCodes.Status200OK;
                    context.Response.Body.Write(buffer, 0, buffer.Length);
                    return;
                }
                else if (_method == "post")
                {
                    var userModel = new CustomSwaggerAuth(context.Request.Form["userName"], context.Request.Form["userPwd"]);
                    if (!options.SwaggerAuthList.Any(e => e.UserName == userModel.UserName && e.UserPwd == userModel.UserPwd))
                    {
                        await context.Response.WriteAsync("login error!");
                        return;
                    }
                    //context.Response.Cookies.Append("swagger_auth_name", userModel.UserName);
                    context.Response.Cookies.Append(SWAGGER_ATUH_COOKIE, userModel.AuthStr);
                    context.Response.Redirect($"/{options.RoutePrefix}");
                    return;
                }
            }
            else if (_path == $"/{options.RoutePrefix}/logout")
            {
                //退出
                context.Response.Cookies.Delete(SWAGGER_ATUH_COOKIE);
                context.Response.Redirect($"/{options.RoutePrefix}/login.html");
                return;
            }
            else
            {
                //若未登录则跳转登录
                if (!options.SwaggerAuthList.Any(s => !string.IsNullOrEmpty(s.AuthStr) && s.AuthStr == context.Request.Cookies[SWAGGER_ATUH_COOKIE]))
                {
                    context.Response.Redirect($"/{options.RoutePrefix}/login.html");
                    return;
                }
            }
            await next();
        });
        app.UseSwagger();
        app.UseSwaggerUI(c=>{
            if (options.SwaggerAuthList.Count > 0)
            {
                //index.html中添加ConfigObject属性
                c.ConfigObject["customAuth"] = true;
                c.ConfigObject["loginUrl"] = $"/{options.RoutePrefix}/login.html";
                c.ConfigObject["logoutUrl"] = $"/{options.RoutePrefix}/logout";
            }
        });
        app.UseMvc();
    }

index.html 添加退出按钮

if (configObject.customAuth) {
  var logOutEle = document.createElement('button')
  logOutEle.className = 'btn '
  logOutEle.innerText = '退出'
  logOutEle.onclick = function() {
    location.href = configObject.logoutUrl
  }
  document.getElementsByClassName('topbar-wrapper')[0].appendChild(logOutEle)
}

自定义的 index.html,login.html

注意:需要将其改为内嵌资源(属性->生成操作->嵌入的资源)

完整 Demo 下载

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极乐技术社区

小程序支付详解+源码(客户端+服务端)

小程序的支付调通,和大家分享下(坑) 包括小程序端、java服务器端 和其他方式的微信支付方式区别不大,也都需要经过统一下单、支付结果通知(回调),具体流程如...

2355
来自专栏我有一个梦想

设计模式学习笔记-命令模式

1. 概述   将一个请求封装为一个对象(即我们创建的Command对象),从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的...

19710
来自专栏菩提树下的杨过

CKEditor/CKFinder升级心得

这几天把一个旧项目中的fckeditor升级为ckeditor 3.2 + ckfinder 1.4.3 组合,下面是一些升级心得: 一、CKFinder的若干...

3367
来自专栏智能大石头

NewLife.Redis基础教程

X组件缓存架构以ICache接口为核心,包括MemoryCache、Redis和DbCache实现,支持FX和netstandard2.0! 后续例程与使用说明...

963
来自专栏NetCore

Linq to Sql 更新数据时容易忽略的问题

越来越多的朋友喜欢用Linq to Sql来进行开发项目了,一般我们都会遇到CRUD等操作,不可否认,在查询方面Linq真的带来很大的便利,性能方面也表现不错,...

2038
来自专栏博客园

.NET Core 使用RabbitMQ

  RabbitMQ是一个开源的,基于AMQP(Advanced Message Queuing Protocol)协议的完整,可复用的企业级消息队列(Mess...

1373
来自专栏Jed的技术阶梯

zookeeper编程01-循环监听

客户端发起对节点的事务操作(以NodeChildrenChanged事件为例) 服务端监听到对应的事件后进行相应的操作

3212
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(66)-MVC WebApi 用户验证 (2)

前言: 回顾上一节,我们利用webapi简单的登录并进行了同域访问与跨域访问来获得Token,您可以跳转到上一节下载代码来一起动手。 继续上一篇的文章,我们...

3888
来自专栏Core Net

ASP.NET Core 2.1 : 十四.静态文件与访问授权、防盗链

我的网站的图片不想被公开浏览、下载、盗链怎么办?本文主要通过解读一下ASP.NET Core对于静态文件的处理方式的相关源码,来看一下为什么是wwwroot文件...

1252
来自专栏大内老A

WCF后续之旅(9):通过WCF的双向通信实现Session管理[上篇]

我们都知道,WCF支持Duplex的消息交换模式,它允许在service的执行过程中实现对client的回调。WCF这种双向通信的方式是我们可以以Event B...

1907

扫码关注云+社区