ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Http Module을 이용한 Custom Authentication(커스텀인증) 구현
    Development/ASP.NET 2020. 6. 6. 20:57

     HTTP 인증은 Authorization 헤더 안의 Basic(기본인증), Bearer(OAuth) 등의 스킴에 해당하는 정보를 기반으로 이루어지게 됩니다.
    IIS에서는 폼인증, 가장인증, 기본인증 등 다양한 인증 방식을 제공하고 있는데, 기본적으로 지원하는 인증방식 외에 HTTP Module을 이용하여 커스텀 인증을 구현하는 예제를 소개시켜 드리려고 합니다.

    먼저 HttpModule 인터페이스를 구현하는 모듈을 만들어야 합니다.
    아래 예제는, 기본 인증을 통과한 이후에 추가적으로 IP 및 API Key정보의 유효성을 검증하도록 구현되어 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    using System;
    using System.Web;
    using System.Data;
    using System.Text;
    using System.Web.UI;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Security.Principal;
    using System.Threading;
     
    using System.Collections;
    namespace MyAuth
    {
        public class CustomAuthentication : IHttpModule
        {
            private const string Realm = "AuthKey";
            public static Hashtable _ApiKey = Hashtable.Synchronized(new Hashtable());
            public void Init(HttpApplication context)
            {
                context.AuthenticateRequest += OnApplicationAuthenticateRequest;
                context.EndRequest += OnApplicationEndRequest;
            }
            private static void SetPrincipal(IPrincipal principal)
            {
                Thread.CurrentPrincipal = principal;
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = principal;
                }
            }
            //Credential Validate
            private static bool CheckCredentials(string credentials, string clientIP)
            {
                bool check = false;
                string sAPIKey = ConfigurationManager.AppSettings["CustomAuthenticationKey"].ToString();
                string[] arrAcessIP = ConfigurationManager.AppSettings["AcessIP"].ToString().Split(',');
                
                //IP 체크 및 API Key 체크
                if(arrAccessIP.Contains(clientIP) && credentials.Equals(sAPIKEY))
                    check = true;
     
                return check;
            }
            //인증에 필요한 추가 자원 추출
            private static void Authenticate(string credentials)
            {
                try
                {
                    string decodedCredentials = Encoding.UTF8.GetString(Convert.FromBase64String(credentials));
                    HttpContext context = HttpContext.Current;
                    string clientIP = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; // Proxy 등을 타고올때, 원본 IP 정보를 헤더에 넘겨줄 경우
                    if (string.IsNullOrEmpty(clientIP))
                    {
                        clientIP = context.Request.ServerVariables["REMOTE_ADDR"];
                    }
                    else
                    {
                        // L4 및 여러 네트워크 장비를 통해서 유입 될 경우 , 형태로 IP가 들어 올 수 있음
                        string[] addresses = clientIP.Split(',');
                        if (addresses.Length != 0)
                        {
                            clientIP = addresses[0];
                        }
                    }
                    if (string.IsNullOrEmpty(clientIP))
                        clientIP = context.Request.UserHostAddress;
                    if (CheckCredentials(decodedCredentials, clientIP))
                    {
                        var identity = new GenericIdentity(decodedCredentials);
                        SetPrincipal(new GenericPrincipal(identity, null));
                    }
                    else
                    {
                        HttpContext.Current.Response.StatusCode = 401;
                    }
                }
                catch (FormatException e)
                {
                    HttpContext.Current.Response.StatusCode = 401;
                }
            }
            private void OnApplicationAuthenticateRequest(object sender, EventArgs e)
            {
                var request = HttpContext.Current.Request;
                var authHeader = request.Headers["Authorization"];
                if (authHeader != null)
                {
                    var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
                    if (authHeaderVal.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && authHeaderVal.Parameter != null)
                    {
                        Authenticate(authHeaderVal.Parameter);
                    }
                    else
                    {
                        HttpContext.Current.Response.StatusCode = 401;
                    }
                }
                else
                {
                    HttpContext.Current.Response.StatusCode = 401;
                }
            }
            private void OnApplicationEndRequest(object sender, EventArgs e)
            {
                var response = HttpContext.Current.Response;
                if (response.StatusCode == 401)
                {
                    response.Headers.Add("WWW-Authenticate"string.Format("Basic realm=\"{0}\"", Realm));
                }
            }
            public void Dispose()
            {
            }
            public CustomAuthentication()
            {
            }
        }
    }
    cs

     

    저는 Soap WebService 형태로 API를 구현하였기 때문에, SoapHeader를 상속받는 APIKey에 해당하는 인증 클래스를 별도로 추가 구현 하였습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using System;
    using System.Web;
    using System.Data;
    using System.Text;
    using System.Web.UI;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Security.Principal;
    using System.Threading;
    using System.Collections;
    namespace OrgWebAPI
    {
        public class Authentication : System.Web.Services.Protocols.SoapHeader
        {
            private string _APIKey = string.Empty;
            public string APIKey { get { return _APIKey; } set { _APIKey = value; } }
            public Authentication()
            {
            }
        }
    }
    cs

     

    커스텀모듈을 적용하기 위하여, Web.config에 아래와 같이 설정을 해줍니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <appSettings>
        <!-- Custom Authentication-->
        <add key="CustomAuthenticationKey" value="2F9F15BF-2A63-EC0E-EA22-8EAF55E5BD4B" />
        <add key="AcessIP" value="127.0.0.1,123.111.111.2,121.111.1.2" />
      </appSettings>
      <system.web>
        <webServices>
          <protocols>
            <add name="HttpPost" />
          </protocols>
        </webServices>
        <compilation debug="true" targetFramework="4.5.2"/>
        <httpRuntime targetFramework="4.5.2"/>
        <httpModules>
          <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/>
        </httpModules>
      </system.web>
      <system.webServer>
        <handlers>
          <add name="ScriptHandlerFactory" verb="*" path="*.asmx"
               type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
               resourceType="Unspecified"/>
        </handlers>
        <!-- 커스팀 인증 모듈 추가 -->
        <modules>
          <add name="CustomAuthentication" type="MyAuth.CustomAuthentication"/>
        </modules>
        <validation validateIntegratedModeConfiguration="false"/>
      </system.webServer>
    </configuration>
    cs

    기본인증의 경우 Basic 스킴에 아이디/패스워드 정보를 인코딩 하여 전달하기 때문에 보안에 취약 할 수 있습니다.

    커스텀 인증 모듈을 통해 API Key 와 IP 등을 추가체크하여 보안을 좀더 강화해보는것은 어떨까요?

    댓글

2017 TIFY Team All Rights Reserved.