一個使用 ASP.NET Core 構建的 API 的 Swagger 工具。直接從您的路由,控制器和模型生成漂亮的 API 文檔,包括用於探索和測試操做的 UI。
項目主頁:https://github.com/domaindrivendev/Swashbuckle.AspNetCore
劃重點,使用多看看 Readme,而後看下項目官方示例,遇到問題找找 issues
繼上篇Swashbuckle.AspNetCore3.0 的二次封裝與使用分享了二次封裝的代碼,本篇將分享如何給文檔添加一個登陸頁,控制文檔的訪問權限(文末附完整 Demo)html
在此以前的接口項目中,若使用了 Swashbuckle.AspNetCore,都是控制其只在開發環境使用,不會就這樣將其發佈到生產環境(安全第一) 。
那麼,怎麼安全的發佈 swagger 呢?我有兩種想法git
大佬如有更好的想法,還望指點一二github
下面我將介紹基於 asp.net core2.1 且使用了 Swashbuckle.AspNetCore3.0 的項目種是怎麼去實現安全校驗的
經過本篇文章以後,能夠放心的將項目中的 swagger 文檔發佈到生產環境,並使其可經過用戶名密碼去登陸訪問,得以安全且方便的測試接口。安全
前面已經說到,須要一個攔截器,而這個攔截器還須要是全局的,在 asp.net core 中,天然就須要用到的是中間件了app
步驟以下,在 UseSwagger 以前使用自定義的中間件
攔截全部 swagger 相關請求,判斷是否受權登陸
若未登陸則跳轉到受權登陸頁,登陸後便可訪問 swagger 的資源asp.net
若是項目自己有登陸系統,可在自定義中間件中使用項目中的登陸,
沒有的話,我會分享一個簡單的用戶密碼登陸的方案dom
Demo 以下圖所示async
在寫此功能以前,已經封裝了一部分代碼,此功能算是在此以前的代碼封裝的一部分,不過是後面完成的。文中代碼刪除了耦合,和 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); } } }
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(); }
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) }
注意:須要將其改成內嵌資源(屬性->生成操做->嵌入的資源)