在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)html

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

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

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

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

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

实现思路

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

步骤以下,在 UseSwagger 以前使用自定义的中间件
拦截全部 swagger 相关请求,判断是否受权登陆
若未登陆则跳转到受权登陆页,登陆后便可访问 swagger 的资源asp.net

若是项目自己有登陆系统,可在自定义中间件中使用项目中的登陆,
没有的话,我会分享一个简单的用户密码登陆的方案dom

Demo 以下图所示async

图片

为使用 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.htmlpost

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 下载

相关文章
相关标签/搜索