在asp.net core2.1中添加中間件以擴展Swashbuckle.AspNetCore3.0支持簡單的文檔訪問權限控制

Swashbuckle.AspNetCore3.0 介紹

一個使用 ASP.NET Core 構建的 API 的 Swagger 工具。直接從您的路由,控制器和模型生成漂亮的 API 文檔,包括用於探索和測試操做的 UI。
項目主頁:github.com/domaindrive…
劃重點,使用多看看 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 下載

相關文章
相關標籤/搜索