只有累積,沒有奇蹟

2022年4月28日 星期四

[WEB API] Swagger - 在 Headers 中新增 API Token 驗證

問題
在開發 API 時都會在網站加上 API Token 機制,當收到一個 Request 請求時 API 會驗證 Token 的正確性,確認請求參數中的 Token 是否是有效 / 已授權 / 有沒有過期或是用來當 SSO (Single sign-on) 的使用,驗證無誤後才會進入接口的邏輯處理目前公司內部 API 專案也有驗證 Token 的設計之前文章介紹了 Swagger 基本使用 發現在線上文件進行 API 測試沒有 Token 的話根本無法測試,此篇記錄如果遇到此問題時該如何解決

解決方案
Swagger 在產生 API 接口會將參數 (Model) 資訊顯示在 html 文件,但如果驗證 Token 機制是在 Header時所生成的 Web API 文件就不會生成在網頁上,寫了一個簡單的範例專案讓大家比較好了解 (僅示意,相信寫法上有更多好的驗證機制)如下圖Code 內容所示,程式碼在進入時就檢查 Request Header 是否有提供 Token 參數,此時使用 Swagger 就是沒有地方可以輸入 Token 參數,因此測試就會因為不符合 Token 驗證會一直爆炸
 
該如何解決呢? 在 Swashbuckle 是很容易擴充的,可以透過加入 IOperationFilter 達到在線上文件新增驗證 Header 的需求,更符合測試上的情境,以下為研究後的小小心得

新增類別並實作 IOperationFilter 
以範例程式為例新增 HeaderTokenOperationFilter 類別並實作 IOperationFilter 介面中的方法 Apply
public class HeaderTokenOperationFilter : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
 if (operation.parameters == null)
  operation.parameters = new List();

 operation.parameters.Add(new Parameter
 {
  name = "Token",
  @in = "header", //query
  type = "string",
  description = "User Token In Header",
  required = true
 });
    }
}

程式說明 : 
  • Line 10 : 要加入的 Parameter 名稱
  • Line 11 : 請求時所要放的位置,此範例是在 Header,也可以放在 QueryString
  • Line 12 : 參數型態
  • Line 13 : Parameter 說明
  • Line 14 : 是否必填
設定 SwaggerConfig
開啟 SwaggerConfig.cs 檔案加入下列程式
驗證
重新開啟專案,發現 Swagger 已經有 Token 參數可以提供輸入並會顯示為必填欄位
為了驗證是否真的有在 Request 的 Headers 帶 Token 參數,可以透過 Chrome F12 來觀察是否有帶正確的 Token 參數 : F12 > Network > Headers 
驗證無誤,打完收工

完整 Sample
完整的 Code 有需要請自行服用
    public class HomeController : ApiController
    {
        public string _userSsoToken = "1aa2a116-876e-464b-bdaf-d6d3adaeb4e4";
        
        /// 
        /// 取得使用者金額
        /// 
        /// 
        [HttpPost]
        [Route("GetMoney")]
        [SwaggerRequestExample(typeof(MemberUser), typeof(MemberUserExample))]
        public IHttpActionResult GetMoney(MemberUser input)
        {
            var userInfo = new UserInfo();
            
            // 檢查 Header 是否有 token
            if (Request.Headers.Contains("Token"))
            {
                string token = Request.Headers.GetValues("Token").First();

                if (token == _userSsoToken)
                {
                    // 驗證無誤 回傳資料
                    if (input.UserId == 9487)
                    {
                        GetUserInfo(userInfo);
                    }
                }
                else
                {
                    // 驗證錯誤
                    TokenError(userInfo);
                }

                userInfo.Token = token;
            }
            
            return Ok(userInfo);
        }

        private static void TokenError(UserInfo userInfo)
        {
            userInfo.Code = "999";
            userInfo.Message = "Token error";
        }

        private static void GetUserInfo(UserInfo userInfo)
        {
            userInfo.Code = "000";
            userInfo.Message = "Success";
            userInfo.UserId = "9487";
            userInfo.Name = "marcus";
            userInfo.Money = "1000";
        }
    }
    
    internal class UserInfo
    {
        public string UserId { get; set; }
        public string Name { get; set; }
        public string Money { get; set; }
        public string Code { get; set; }
        public string Message { get; set; }
        public string Token { get; set; }
    }

    public class MemberUser
    {
        public int UserId { get; set; }
    }

    public class MemberUserExample : IExamplesProvider
    {
        public object GetExamples()
        {
            return new MemberUser
            {
                UserId = 9487,
            };
        }
    }
    public class HeaderTokenOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            if (operation.parameters == null)
                operation.parameters = new List();

            operation.parameters.Add(new Parameter
            {
                name = "Token",
                @in = "header",
                type = "string",
                description = "User Token In Header",
                required = true
            });
        }
    }
    參考
    add-an-authorization-header-to-your-swagger-ui-with-swashbuckle
    .NET -Swagger Web API for Basic Authentication

    0 意見:

    張貼留言

    Copyright © m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com