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
注意:需要將其改為內嵌資源(屬性->生成操作->嵌入的資源)