Problem

기존 오래된 코드의 리펙토링 때문에 시작된 작업이다. ASP.NETWeb API1를 이용해서 구현이 되어있었고 다른 외부 서비스와(사전에 미리 프로토콜이 약속된 대상과) 통신을 하고 있었다. 보안의 이유로 각 웹 메소드는 파라미터와 추가로 파라미터를 검증할 수 있는 추가 파라미터(HMAC1과 비슷한)를 입력하여 요청을 검증하고 있었다. 아래는 기존 나쁜 냄새가 났던 코드의 예시이다. 동작하는 실제 코드는 아니다.

public ActionResult Method1(String param1, String param1, String hmac)
{
    // validation
    if(hmac(param1, param2) != hmac) {
        throw Error
    }

    return ...
}

public ActionResult Method2(String param1, String param1, String param2, String hmac)
{
    // validation
    if(hmac(param1, param2, param3) != hmac) {
        throw Error
    }

    return ...
}

validation 블럭이 매번 반복되는 것을 볼 수 있었고, 개선해야할 필요가 있었다.

Solution

  1. C# 에서도 Java의 어노테이션 같은 Attribute를 가능한지 먼저 확인해봤다2.
    • 어셈블리, 형식, 메서드, 속성에 적용할 수 있는 방법이 있음
    • Attribute를 연결하면 프로그램 엔터티와 연결되면 리플렉션이라는 기법을 사용하여 런타임에 확인됨
    • 즉, 아래처럼 사용할 수 있었다.
       [HMACParamsValidator]
       public ActionResult Method1(String param1, String param1, String hmac)
       {
        return Json(new { Result = ChargeService.RollbackResultValue.SUCCESS.ToString() }, JsonRequestBehavior.DenyGet);
       }
      
  2. 원하는 동작을 하기 위한 HMACParamsValidator를 구현한다.
    • OnActionExecuting 에서 입력받은 파라미터를 확인하고 hmac 파라미터는 별도 처리할 수 있다.
     using System;
     using System.Collections.Generic;
     using System.Reflection;
     using System.Web.Mvc;
    
     namespace App_Code
     {
         [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)]
         internal class HMACParamsValidatorAttribute : ActionFilterAttribute
         {
             public HMACParamsValidatorAttribute()
             {
             }
    
             public override void OnActionExecuted(ActionExecutedContext filterContext)
             {
                 base.OnActionExecuted(filterContext);
             }
    
             public override void OnActionExecuting(ActionExecutingContext filterContext)
             {
                 base.OnActionExecuting(filterContext);
    
                 List<String> list = new List<String>();
                 String hmac = "";
                 foreach (KeyValuePair<string, object> items in filterContext.ActionParameters)
                 {
                     if (items.Key.Equals("hmac", StringComparison.CurrentCultureIgnoreCase))
                     {
                         hmac = items.Value.ToString();
                     }
                     else
                     {
                         if (items.Value != null)
                         {
                             list.Add(items.Value.ToString());
                         }
                     }
                 }
    
                 list.Add(Key);
    
                 if(hmac != hmac(list)) // hmac 구현 필요 (예시)
                 {
                     filterContext.Result = new JsonResult() {
                         Data = NOT_MATCH_HMAC.ToString(),  // NOT_MATCH_HMAC 정의 필요
                         JsonRequestBehavior = JsonRequestBehavior.AllowGet
                     };
                 }
             }
    
             public override void OnResultExecuted(ResultExecutedContext filterContext)
             {
                 base.OnResultExecuted(filterContext);
             }
    
             public override void OnResultExecuting(ResultExecutingContext filterContext)
             {
                 base.OnResultExecuting(filterContext);
             }
         }
     }
    

References