라떼군 이야기


.net attribute를 이용한 validation 구현

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