세션 저장소 커스터마이징

지금까지 살펴본 바 세션 정보를 memcached를 이용하여 세션 정보를 성능과 관리, 그리고 확장가능성 측면에서 만족할 만한 솔루션이다.

여기에서 좀 더 나아가 쿠키 등을 이용하여 서브 도메인(Sub Domain)의 웹 응용 프로그램에 인증을 하거나 브라우저를 닫고 새로운 브라우저로 재접속 한 경우 기존 세션을 유지할 수 있도록 기능을 개선할 수 도 있다.

과거에는 SSO(Single-Sign-On)을 구현하기 위해 쿠키로 서브 도메인을 인증하는 경우 domain 에 의해 쿠키가 공유가 가능하다는 점을 이용하여 구현하기도 했다. 물론, SSO 솔루션들이 많이 있었지만, 수천 수만명의 사용자가 관리 대상이 아니라면 굳이 비싼 SSO 솔루션을 쓸 필요는 없었다.

세션 저장 방법 커스터마이징

ASP.NET MSSQL Session Table 또는 Windows Azure SQL Session Table은 다음과 같이 정의된다. (MSDN에 정의된 테이블 참조 1)

CREATE TABLE [ASPState].dbo.ASPStateTempSessions (
    SessionId         nvarchar(88)  NOT NULL PRIMARY KEY,
    Created          datetime       NOT NULL DEFAULT GETUTCDATE(),
    Expires          datetime       NOT NULL,
    LockDate            datetime        NOT NULL,
    LockDateLocal     datetime      NOT NULL,
    LockCookie       int             NOT NULL,
    Timeout          int             NOT NULL,
    Locked           bit             NOT NULL,
    SessionItemShort    VARBINARY(7000) NULL,
    SessionItemLong  image        NULL,
    Flags             int            NOT NULL DEFAULT 0,
)   

CREATE NONCLUSTERED INDEX Index_Expires ON [ASPState].dbo.ASPStateTempSessions(Expires)  

CREATE TABLE [ASPState].dbo.ASPStateTempApplications (
    AppId             int            NOT NULL PRIMARY KEY,
    AppName          char(280)    NOT NULL,
)   

CREATE NONCLUSTERED INDEX Index_AppName ON [ASPState].dbo.ASPStateTempApplications(AppName)

이 두 테이블에서 가장 중요한 것은 다음의 두 개의 Private Key가 되는 필드이다.

  • AppId

    웹 응용 프로그램을 구분하는 고유 Id 값이다. IIS는 여러 개의 웹 응용 프로그램을 호스팅할 수 있는데, 이 웹 응용 프로그램을 구분하는 값으로 사용된다.

  • SessionId

    웹 응용 프로그램 내에 세션 키로 사용되는 값이다. 일반적으로 이 값은 해쉬된 값으로 중복되지 않는다.

따라서 여러 웹 응용 프로그램에서 같은 SessionId를 사용할 수 있다면 인증에 대한 SSO(Single-Sign-On)은 구현되는 것과 마찬가지다. 그러므로 위의 테이블 중 dbo.ASPStateTempApplications 테이블은 없어져도 된다.

만약 MS SQL을 사용하여 세션 저장소로 이용하는 경우 SQL Session Provider가 사용하는 Stored Procedure의 Where 절에 AppId를 빼기만 하면 된다.

필자는 SQL Server가 아닌 memcached를 이용하므로 위의 구성은 굳이 생략이 가능하다.

MemCached Session Store Provider 커스터마이징

ASP.NET은 클라이언트(웹 브라우저를 사용하는 사용자)를 구분하기 위해 해시된 세션 키 값을 사용한다고 했다. 이 해시된 값은 없어도 되지만, 웹 응용 프로그램 내부적으로 세션을 사용한다면 반드시 필요한 키 값이다.

클라이언트에게는 서버 내부적으로 사용하는 해시된 키 값을 쿠키에 저장하고, 서버로 요청이 오는 경우 이 쿠키 값의 해시된 세션 키 값을 이용하여 세션 데이터를 조회하게 된다. 만약, 이 해시된 세션 키 값이 없다면 새로운 세션으로 인식한다.

SSO를 구현하기 위해서 신원이 확인된 사용자마다 완전히 유일한(Unique)한 키 값이 필요하다. 예를 들어, 이 값은 사용자의 고유 번호가 될 수 있고, 사용자 아이디 또는 이메일과 같은 유일한 값이 되어야 한다. 세션 키는 매번 변할 수 있는 값이기 때문에 유일한 값이긴 하나 사용자마다 변하지 않는 유일한 값이 될 수 없다.

이를 구현하는 방법은 매우 간단하다. SessionStateStoreProviderBase 추상 클래스를 구현할 때 Id 값을 고의로 변경하면 된다.

public sealed class MemcachedSessionStateStore : SessionStateStoreProviderBase
{
   void Command(Action action)         
   { 
        var pool = SockIOPool.GetInstance(); 

        // 필자의 원격 memcached IPs     
        pool.SetServers(new string[] { "192.168.0.23:11211", "192.168.0.23:11211", "192.168.0.23:11211" }); 
        pool.InitConnections      = 3;
        pool.MinConnections       = 3;
        pool.MaxConnections       = 5;
        pool.SocketConnectTimeout = 1000;
        pool.SocketTimeout        = 3000;
        pool.MaintenanceSleep     = 30;
        pool.Failover             = true;
        pool.Nagle                = false;
        pool.Initialize();

        action();
   }

   public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
    {
        try
        {
            if (context.User.Identity.IsAuthenticated)
            {
                id = context.User.Identity.Name;
            }

            var data = new SessionData()
                {
                    Id      = id,
                    LockAge = TimeSpan.FromMinutes(10),
                    LockId  = id,
                    Exfires =  DateTime.Now.AddMinutes(10)
                }.ToBinaryBytes().ToBase64();

            Command(() => new MemcachedClient().Set(id, data));

        }
        catch (Exception e)
        {
            // 생략...
        }
        finally
        {
        }
    }

    public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, 
                                                                                    out TimeSpan lockAge, 
                                                                                    out object lockId,
                                                                                    out SessionStateActions actionFlags)
    {
        return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actionFlags);
    }

    public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked,
                                                                                            out TimeSpan lockAge,
                                                                                            out object lockId,
                                                                                            out SessionStateActions actionFlags)
    {
        return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actionFlags);
    }


    public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
    {
       if (context.User.Identity.IsAuthenticated)
        {
            id = context.User.Identity.Name;
        }

        var data = new SessionData()
        {
            Id = id,
            LockAge = TimeSpan.FromMinutes(10),
            LockId = id,
            Exfires = DateTime.Now.AddMinutes(10)
        }.ToBinaryBytes().ToBase64();

        Command(() => new MemcachedClient().Set(id, data));
    }

    public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
    {
        return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), (int)timeout);
    }

    private string Serialize(SessionStateItemCollection items)
    {
        var ms = new MemoryStream();
        var writer = new BinaryWriter(ms);

        if (items != null)
            items.Serialize(writer);

        writer.Close();

        return Convert.ToBase64String(ms.ToArray());
    }


    private SessionStateStoreData Deserialize(HttpContext context, string serializedItems, int timeout)
    {
        var ms = new MemoryStream(Convert.FromBase64String(serializedItems));
        var sessionItems = new SessionStateItemCollection();

        if (ms.Length > 0)
        {
            var reader = new BinaryReader(ms);
            sessionItems = SessionStateItemCollection.Deserialize(reader);
        }

        return new SessionStateStoreData(sessionItems, SessionStateUtility.GetSessionStaticObjects(context), timeout);
    }  

// 이하 생략...  
// 이하 생략...  
// 이하 생략...

}  

인증된 사용자와 인증되지 않은 사용자의 세션 데이터

필자는 memcached의 사용자 세션 키를 다음과 같이 정의하여 구현하였다.

  1. 인증되지 않은 사용자는 해쉬된 세션 키를 사용한다.
  2. 인증된 사용자는 인증 정보를 세션 키로 사용한다. (UserName 정보)

모든 클라이언트의 웹 브라우저 쿠키에 ASP.NET 세션 키가 ASP.NET_SessionId 쿠키 값으로 저장된다. 이 값은 ASP.NET이 생성한 해쉬된 값이므로 언제든지 변할 수 있다가도, 사용자가 웹 응용프로그램에서 인증을 하게 되면 해쉬된 세션 키를 사용하지 않고 사용자 계정으로 세션 키 값을 대체하게 된다.

이 코드가 위의 코드 중에 다음과 같이 구현한 부분이다.

if (context.User.Identity.IsAuthenticated)
{
    id = context.User.Identity.Name;
}

그럼 현재까지 구현된 부분으로 다음과 같은 시나리오로 테스트를 하고 결과를 보자.

  1. 익명으로 웹 사이트 접속
  2. 익명 사용자의 세션 키 값을 memcached 에서 조회 (해쉬된 세션 키 rlvh3y4edt1nyzrt42sdgnvu)
  3. 웹 사이트 로그인 (로그인 사용자 계정 powerumc)

  4. 인증된 사용자의 계정으로 memcached 에서 조회

그럼 분산된 웹 응용 프로그램이나 다른 도메인의 웹 응용 프로그램 간에 서로 인증을 해보자. 폼 인증을 사용한 웹 응용 프로그램이므로 다른 웹 응용 프로그램에 폼 인증을 시킬 수 있는 방법만 있으면 된다. 웹 응용 프로그램 간에 토큰 값을 넘길 수 도 있고, 또는 요즘 유행하는 다른 인증 매커니즘을 이용할 수 도 있다.

어쨌든 서로 다른 웹 응용 프로그램이 하나의 memcached 세션 서버에 연결이 가능하다면 신원이 인증된, 위에’서 테스트 한 사용자 계정 ‘powerumc’는 어느 웹 응용 프로그램에서 세션을 조회하더라도 존재하게 된다.

사용자는 웹 브라우저를 완전히 닫거나 운영체제를 완전히 재시작하였다고 하더라도 세션이 만료되는 통상적인 시간인 약 20분 이전에 로그인만 한다면 이전에 저장된 세션 데이터를 가져와 마지막의 최신 상태를 유지할 수 있게 된다.

결론

지금까지 memcached를 이용하여 Session State Store Provider를 만들고 이를 활용하는 방법을 알아보았다. 분산된 세션 저장소를 사용해야 하는 경우 memcached를 이용하면 신뢰된 성능을 보장받을 수 있고, 세션 데이터를 유지하는 방법을 변형하여 좀 더 보안을 높이거나 유연하게 상호운용을 가능하도록 할 수도 있다.

  • 분산된 세션 저장소를 확장
  • memcached를 이용하여 신뢰할 수 있는 성능 발휘
  • 세션의 보안을 강화하거나 격리시킬 수 있는 방법이 가능
  • 세션의 상호운용성을 높여 서로 다른 도메인간에 인증 가능
  • 서로 다른 도메인간에, 서로 다른 플랫폼 간에 세션 데이터 공유 가능

이 외에 여러 가지 기법들을 활용하여 더 많은 것들을 가능할 수 있다. 세션의 활용이 웹 개발 플랫폼에서 그만큼 중요하고 보안과 직결되는 요소인 만큼 이 기회에 세션에 대한 내용을 모두 정복해 보기 바란다.

  1. MSDN에 정의된 테이블 참조 http://msdn.microsoft.com/en-us/library/aa478952.aspx


Posted by 땡초 POWERUMC

댓글을 달아 주세요

필자는 일전에 이와 관련되어 상당한 분량의 포스팅을 올린 적이 있다. 총 5회의 아티클 중 마지막 회를 모두 작성하지는 못했지만, 지금 이 내용이 그 마지막 회의 내용과 어느 정도의 내용과 유사하다고 보면 된다.


그 중, 4회 아티클 ‘[실전 ASP.NET Session [4] - 세션상태 마이그레이션]5’의 내용은 본 아티클의 내용에서 매우 중요한 기초 내용이 된다. 이 글을 읽고 있는 독자 중 잘 이해가 되지 않는다면 필자가 이전에 작성한 포스팅을 반드시 읽어보기를 바란다.

그리고 위의 필자가 작성한 링크의 아티클은 5년전에 작성된 글임을 인지해 주길 바란다. 그 때는 필자가 지금보다도 더 실력이 형편 없었을 뿐더러 현재 추구하는 웹 개발의 트랜드와 상이한 면이 있을 수도 있을 것이다. .NET Framework 버전과 개발툴의 버전도 지금 사용하는 버전과는 한참 예전 버전이었다. 하지만 5년이 지난 글임에도 기초적인 내용은 모두 탄탄하게 짚고 넘어가므로 한번씩 보는 것도 나쁘지 않다.

ASP.NET이 지원하는 분산 세션 상태(Session State)

일반적으로 1대 1의 물리적인 관계는 분명 분산환경이긴 하지만 분산 환경이라고 말하지 않는다. 어떤 물리적인 환경에 배치하든, 통계학의 분산에 대한 기대값이나 편차의 해는 변하지 않기 때문이다.

분산 환경에서 더 나은 성능을 내기 위한 알고리즘과 휘발성인 메모리 안에서 데이터가 유실되지 않도록 원자성(Atomicity)을 유지할 수 있는 방법을 제공해야 한다. 또 웹 서버의 세션이라는 특징은 매우 빈번하게 엑세스 되고, 업데이트와 삭제 작업도 매우 많이 발생한다.

하지만, 안타깝게도 memcached[1]는 ‘get’ 명령의 원자성(Atomicity)를 보장하지 않는다고 한다.

A series of commands is not atomic. If you issue a 'get' against an item, operate on the data, then wish to 'set' it back into memcached, you are not guaranteed to be the only process working on that value. In parallel, you could end up overwriting a value set by something else.


하지만 괜찮다. 웹 서버의 세션은 동시다발적인 병렬 작업으로 요청이 들어올 수 없는 구조이다. 웹 서버로 사용자의 요청이 들어올 때, 사용자의 웹 브라우저에 가진 쿠키 값이 신원보증을 하는 값, 또는 해시된 세션의 키 값이다. 그러므로 한 세션에 대해 동시에 여러 클라이언트(사용자 웹 브라우저)의 요청은 있을 수 없는 일이다.

하지만 불가능한 것도 아니다. 예전에는 자바스크립트가 허용되는 게시판이나 이와 유사한 환경에서 악성 스크립트를 심어 놓으면, 그 글을 보는 누군가는 브라우저의 쿠키 값을 취득하여 해커에게 보내고, 해커는 세션이 만료되는 20분(일반적인 세션 만료 시간) 전에 취득한 세션 값으로 재 요청을 하게 되면 다시 세션이 연장되고, 세션이 로그인된 상태인 경우 해커도 로그인된 상태로 입장하는 것이 가능하다

때문에 완전히 같은 해시된 세션의 키 값으로 여러 클라이언트(사용자의 웹 브라우저)의 요청이 온다는 것은 사용자의 세션 값이 털려서, 누군가 악용하고 있다고 보는 것이 맞다.

그러므로 아주 정상적이고 일반적인 환경에서 원자성을 제공하지 않는 ‘get’ 명령은 전혀 문제가 되지 않으며, 오히려 원자성을 보장할 수 없는 문제로 더 좋은 Read 성능을 낼 수 있기 때문에, memcached 가 세션 서버로 사용하기에 redis[2] 보다 더 좋을 수 있다.

ASP.NET이 제공하는 세션 관리 요약

아무런 설정이 없다면 In-Proc, 즉 로컬 머신의 메모리를 사용하게 된다. 만약 불행히도, Win32 DLL을 참조하는 웹 응용 프로그램이라면 웹 서버의 메모리의 크기 따윈 무시하고 최대 2GB 밖에 사용할 수 없게 된다.

이를 극복하는 방법으로 윈도우 서비스로 백그라운드로 실행되는 ASP.NET Session State Service 서비스를 사용하면 좋다. 여러 웹 응용 프로그램이 하나의 Session State Service에 연결되더라도 웹 응용 프로그램마다 고유의 Identity Key(Guid)가 할당되고, 이를 Primary Key로 구분하게 된다. 이 NT Services가 다운되거나 재시작하지 않는 이상 웹 서버의 세션은 항상 유지할 수 있다.

긴급한 패치로 웹 응용 프로그램을 업데이트 해야 하는 경우, IIS가 재시작 되는 경우가 발생할 수 있는데 이 때 Worker Process에 의해 구동되는 In-Proc 세션 정보는 모두 초기화된다. 웹사이트에서 뭔가를 구매하려는 사용자가 있었다면 큰 사고로 이어질 수 있지만, Session State Service에 세션 정보가 저장이 되므로 IIS 재시작 후에도 웹 응용 프로그램은 세션 정보를 모두 안전하게 유지할 수 있다.

보통 웹 서버와 Session State Service간에 통신을 하기 위해 방화벽의 특정 포트를 개방해야 하고, 내부적으로 서로 간에 소켓 통신에 사용되는 전용 프로토콜이 존재하므로 Session State Service를 확장하기란 쉽지 않을 것이다.

이런 경우, ASP.NET에서 MS SQL Server를 이용하여 세션 정보를 저장하는 방법을 제공해 준다. 세션 정보에 추가하고 싶은 정보나 DB상에 기록되어질 특정 필드를 추가하여 사용할 수 있고, Oracle 또는 MySQL을 사용하여 세션 상태를 저장할 수도 있다.

위의 링크 중 ‘[실전 ASP.NET Session [4] - 세션상태 마이그레이션]13’에 예제로 구현된 Oracle Session Store Provider 예제가 있으니 참고하길 바란다.

memcached를 이용하는 세션 저장소

memcached를 세션 저장소로 이용하는 것은 ASP.NET Session State Services의 역할과 구현만 다를 뿐이지 매우 유사하다. memcached도 메모리를 저장소로 이용하여 빠르게 캐시하고 요청에 빠르게 응답할 수 있는 간결한 구조로 구현된 오픈소스이다.

특히 memcached를 이용할 경우 ASP.NET Session State Services로 불가능한 클러스트링이 가능하기 때문에 수평적으로 세션 서버를 확장할 수 있다. 한 대의 세션 서버만 있는 경우보다 로드벨런싱의 자유도가 더 높고, 세션 서버의 장애에도 대처가 가능하다. 또 하나의 장점이라면 저가형 장비를 병렬로 구성이 가능하기 때문에 세션 서버를 구성하기 위한 금전적이거나 물리적인 많은 제약이 사라지게 된다.

memcached와 redis, 두 가지 오픈소스를 고려하고 있었는데, 세션 서버에 memcached가 더 적합하다고 생각하여 memcached만 다룬다. memcached의 소스 코드가 더 가볍고 취향이나 요구사항에 따라 코드를 변형하기에 더 적합하지 않을까 생각한다. 그리고 자칫 복잡해질 수 있는 구조적인 아키텍처를 좀 더 간결한 memcached로 표현하는 것이 글을 보는 입장이나 쓰는 입장에서 편하다고 믿는다.

memcached는 C언어로 구현이 되어있고, GNU C 표준 라이브러리를 사용하므로 여러 운영체제에서 사용이 가능하다. (ASP.NET Session State Services는 윈도우 환경에서만 사용이 가능하다) 물론 필자는 맥킨토시, 리눅스, 윈도우에서 memcached를 컴파일, 구동이 잘 됨을 확인하였다.

간단하게 사용자의 세션 정보를 저장할 클래스를 하나 간단하게 만들었다. 외부에서는 오직 참조만 하여 사용할 수 있도록 internal 생성 메서드를 하나 가지고 있다.


다음은 ASP.NET MVC 프로젝트로 만든 간단한 예제이다.

web.config
1
2
3
4
5
6
<sessionState mode="Custom" customProvider="MemcachedSessionStateStore">
    <providers>
        <add name="MemcachedSessionStateStore"
             type="Umc.Core.Web.SessionState.Memcached.MemcachedSessionStateStore, Web.SessionState.Memcached, Version=1.0.0.0, PublicKeyToken=eed8f2bc3bfc4c7a, Culture=neutral"/>
    </providers>
</sessionState>

  

HomeController.cs
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
public class HomeController : Controller   
{
   private static readonly string KEY_OF_USER_SESSION = "__USER_SESSION_KEY__";
   public UserIdentity SessionStore
    {
        get { return (Session[KEY_OF_USER_SESSION] as UserIdentity) ?? UserIdentity.Empty; }
        set { Session[KEY_OF_USER_SESSION] = value; }
    }
 
 
    public ActionResult Index()
    {
        Session[KEY_OF_USER_SESSION] = UserIdentity.New("POWERUMC"
                                                        , "Junil, Um"
                                                        , "powerumc at gmail.com");
 
 
         /*
          // 맴버쉽 사용자 인증 설정 생략...
        var ticket = new FormsAuthenticationTicket(...);
        var user = new GenericPrincipal(new FormsIdentity(ticket), ... )
         * */
 
 
        return View();
    }
}

 

위와 같이 클라이언트의 세션 키를 이용하여 memcached의 서버에 세션 키를 이용하여 세션 값을 저장한다.

telnet 'get' 명령 결과
MPOWERUMC:~ powerumc$ telnet 192.168.0.23 11211
Trying 192.168.0.23...
Connected to 192.168.0.23.
Escape character is '^]'.
get laaymm13uyu03bofhdydi4iq
VALUE laaymm13uyu03bofhdydi4iq 0 477
AAEAAAD/////AQAAAAAAAAAMAgAAAF1XZWIuU2Vzc2lvblN0YXRlLk1lbWNhY2hlZCwgVmVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWVlZDhmMmJjM2JmYzRjN2EFAQAAAEpVbWMuQ29yZS5XZWIuU2Vzc2lvblN0YXRlLk1lbWNhY2hlZC5NZW1jYWNoZWRTZXNzaW9uU3RhdGVTdG9yZStTZXNzaW9uRGF0YQQAAAATPElkPmtfX0JhY2tpbmdGaWVsZBg8TG9ja0FnZT5rX19CYWNraW5nRmllbGQYPEV4ZmlyZXM+a19fQmFja2luZ0ZpZWxkFzxMb2NrSWQ+a19fQmFja2luZ0ZpZWxkAQAAAgwNAgAAAAYDAAAAGGxhYXltbTEzdXl1MDNib2ZoZHlkaTRpcQC8oGUBAAAAAPAWAzAi0IgJAwAAAAs=
END

 

 

ASP.NET의 세션이 실제로 저장이 되었는지 확인해보자. telnet을 통해 memcached 포로토콜의 명령을 입력하여 확인하면 된다.

현재 브라우저에서 연 세션 값은 쿠기에 저장이 되어 있다. 쿠키에 저장된 세션의 키 값은 ‘laaymm13uyu03bofhdydi4iq’ 이다.


telnet에 접속하여 memcached에 저장된 세션 키의 값을 조회된다.

memcached 세션 저장소로써 활용

memcached 오픈 소스 솔루션을 이용하여 메모리 캐시를 이용하여 ASP.NET 세션 상태를 저장할 수 있도록 구성해 보았다. 이제 얼마나 좋은 성능을 낼 수 있는지 측정이 필요한데 본 아티클에서는 memcached를 세션 서버로 활용할 때에 대한 성능의 문제는 다음에 기회가 되면 더 심도있게 다루어 보도록 하겠다.

그리고 ASP.NET 뿐만 아니라, JSP 또는 Servlet, 그 외에 여러 웹 개발 프레임워크에서 쉽게 사용할 수 있다. memcached는 C#, Java, Python 등 여러가지 언어로 memcached서버에 연결할 수 있는 클라이언트 라이브러리를 어렵지 않게 찾을 수 있다.

이번 아티클에서는 MemcachedSessionStoreProvider 소스 코드를 제공하지 않았다. 물론, 필자의 위 코드를 실행하기 위해 MemcachedSessionStoreProvider 소스 코드가 있어야 한다. 이 코드는 다음 아티클에서 조금씩 작성해 나갈 예정이다.

memcached 에 대한 자세한 내용은 아래 링크의 구글 코드에 들어가면 컴파일 및 실행 방법과 유지관리 방법에 대한 위키가 있다.

memcached wiki, https://code.google.com/p/memcached/wiki/NewStart



[1]: C언어로 구현된 고성능 분산 메모리 객체를 캐시하는 서버. Free & open source, high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.

[2]: 키/값을 저장할 수 있는 분산 서버로 데이터의 구조체 뿐만 아니라 문자열, lists, 빠른 검색을 위한 hashes 등을 지원. Redis is an open source, BSD licensed, advanced key-value store. It is often referred to as a data structure server since keys can contain stringshasheslistssets and sorted sets.

Posted by 땡초 POWERUMC

댓글을 달아 주세요



[.NET/ASP.NET] - 실전 ASP.NET Session [1] - 쿠키를 이용한 상태관리와 위험성
[.NET/ASP.NET] - 실전 ASP.NET Session [2] - 상태관리의 종류
[.NET/ASP.NET] - 실전 ASP.NET Session [3] - 다양한 세션 관리 방법
[.NET/ASP.NET] - 실전 ASP.NET Session [4] - 세션상태 마이그레이션

 

 

제목에는 세션상태 마이그레이션 이라고 했지만, 보다 구체적으로 언급하면 세션 공급자를 구현하는 단원입니다. 이전에 보았던 아래의 그림과 같이 ASP.NET 이 제공하는 몇 가지의 세션상태 저장소가 있지만, 상황에 따라서 사용할 수 없는 경우도 생길 수 가 있습니다. .NET Framework 3가지의 세션 공급자를 제공해 주지만 어떠한 경우는 이 3가지 모두 쓸 수 없기 때문입니다.

 

[그림1] ASP.NET 이 제공하는 기본적인 세션 공급자

 

만약, ASP.NET 프로젝트가 MS-SQL 데이터베이스 기반으로 구축이 된다면 SQL Server 를 이용한 세션 관리가 가능하지만, Oracle 데이터베이스 기반일 경우 .NET Framework Oracle Session Provider 를 기본으로 제공하지 않기 때문에 세션관리에 이슈가 있다면 프로젝트 착수 단계부터 개발 플랫폼의 선택의 기회에 제외될 수 있기도 합니다. 가령, Oracle / JSP ASP 베이스의 시스템을 닷넷으로 전환하고자 한다면, 세션 서버 구축 때문에 비싼 비용을 지불하여 MS-SQL 을 구입할 수 도 없는 노릇이지요. 또한, 세션에 추가 정보나 필드 등이 추가 되어야 할 경우 기존 상태 저장소는 사용할 수 없게 되기 때문에, 새로운 방안을 모색해야 할 것입니다.

 

ASP.NET 은 이러한 다양한 상태 공급자를 구현할 수 있도록 SessionStateStoreProviderBase 인 추상 클래스를 제공하고 있습니다. 이 클래스는 System.Web.SessionState 네임스페이스에 존재합니다. 우선 이 클래스와 상속 구현을 위해 아래의 MSDN 문서를 참고하시기 바랍니다.

 

세션 상태 저장소 공급자 구현

 

그리고 Access 데이터베이스를 이용한 세션 상태 공급자의 소스를 구현한 MSDN 소스를 참고하시기 바랍니다.

 

법: 샘플 세션 상태 저장소 공급자

 

소스코드가 복잡하지 않기 때문에, 적절한 다른 종류의 데이터베이스로의 전환은 쉽게 하실 수 있으리라 생각합니다.

 

우리가 직접 세션 공급자(Session Provider) 를 구현한다는 것은 많은 의미를 내포할 수 있습니다. 그 중, 가장 의미 있는 것은 이 Custom Session Provider 를 구현함으로써 개발자나 구축하는 프로젝트는 아무런 코드의 수정 없이 데이터베이스 플랫폼의 전환이 가능하다는 것입니다. 단지, web.config 에서 Session Provider 만 지정해주면 됩니다.

 

 

위의 첨부파일은 MSDN 예제의 Access Database Session 을 저장하는 예제인데, MSSQL Database 로 변경한 소스입니다.

web.config 와 테이블의 내용을 보면 다음과 같습니다.

 

[그림2] Custom Session Provider web.config 에 설정

 

[그림3] DB 테이블에 Custom Sessino Provider 가 세션을 저장한 결과

 

위의 web.config connectionString 에 적절한 id password 를 넣어주는 센스만 보여주신다면, 무리 없이 바로 샘플은 동작할 겁니다. ( Database Table 만드는 스크립트는 코드의 주석에 포함되어 있습니다)

 

 

처음 저의 의도와 달리 포스팅을 점점 날로 먹으려고 하는 것 같네요. 원래 이번 포스팅부터 많은 내용을 익혀서 전달해 드리려고 했는데 요즘 Umc.Core 에 올인하고 있기 때문에 포스팅을 성의 있게 쓰기에 시간이 턱없이 부족하네요. 이렇게 올인해도 집에 와서 코딩하면 20 라인 만들기도 정말 벅차답니다. 만들면서 잘못된 설계 때문에 몇 번이나 엎었는지 ^^; “세션 분산처리도 포스팅 최종회에 함께 포함하도록 합니다.

 

다음 포스팅에 실제 SSO 시스템을 적용한 예제도 준비하려고 했지만, 그렇게 하지 못할 것 같아요. 하지만, Custom Session Provider SSO 시스템에 대한 큰 그림과 세부적인 구현방법은 최대한 자세히 기술할 예정이니, 관심 있는 분은 좋은 팁 배워간다 생각하시고 보시면 될겁니다.


Posted by 땡초 POWERUMC

댓글을 달아 주세요



[.NET/ASP.NET] - 실전 ASP.NET Session [1] - 쿠키를 이용한 상태관리와 위험성
[.NET/ASP.NET] - 실전 ASP.NET Session [2] - 상태관리의 종류
[.NET/ASP.NET] - 실전 ASP.NET Session [3] - 다양한 세션 관리 방법
[.NET/ASP.NET] - 실전 ASP.NET Session [4] - 세션상태 마이그레이션

 

 

ASP.NET 은 다양한 세션 관리 방법을 제공하여 줍니다. 서버의 자원은 제한적이기 때문에 In-of-process 방식이 아닌, Out-of-process 의 세션 관리 방법이 필요하다고 이전 시간에 말한바 있습니다. 이런 이유 이외에도, Worker Process 에 의해 세션이 관리된다면 프로세서가 어떤 오류로 인해 종료가 되면 세션도 함께 증발해 버리는 경우도 생깁니다. 이유가 가져다 대면 많지만, 어떤 세션 관리 방법이 있는지 알아보고, 자신의 사이트나 회사의 사이트에 어떻게 적용할 것인지 한번 생각해 봐도 좋을 내용이 될 것입니다.

 

1.    ASP.NET Session State Service

 

ASP.NET Session State Service 라는 서비스가 별도로 존재합니다. 이 서비스는 관리도구->서비스를 통해 프로세서를 시작/종료 할 수 있고, 디폴트로 시작 안함입니다. 이 프로세서는 ASP.NET Worker Process 와는 별도의 프로세서이기 때문에, IIS 가 중단이 되거나 오동작으로 인해 세션이 증발하는 경우는 없습니다.

 

 [그림1] 서비스의 ASP.NET Session State Service

 

위의 [그림1] 에서 시작버튼을 누르게 되면 ASP.NET Session State Service 를 시작할 수 있습니다.

 

우선 여기서, 주의할 부분이 있습니다. ASP.NET Session State Service 를 사용하기 위해서는 processModel 섹션의 webGarden 속성은 반드시 false 이여야 합니다. 갑자기 튀어나온, webGarden 속성은 또 뭐죠?? 이 속성은 다중 프로세서 CPU 에만 적용되는 항목인데 다중 프로세서에서 Worker Process 를 사용할 코어(Core)의 선호도를 지정할 수 있습니다. 만약, ASP.NET Session State Service processModel webGarden 을 함께 설정한다면, 가령 A 라는 세션을 다른 Worker Process 에서 처리를 하는 교착현상(?)이 발생할 수 있습니다.

 

processModel webGarden 참고

<processModel> 구성 문서에 틀린 webGarden 설명과 필드가 포함되어 있다

 

 

webGarden 의 속성을 임의로 바꾸고자 한다면, web.config 에서는 바꿀 수 없습니다. 왜냐하면, machine.config

 

<section name="processModel" type="System.Web.Configuration.ProcessModelSection, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" allowDefinition="MachineOnly" allowLocation="false"/>

 

allowDefinition="MachineOnly" 로 설정되어 있습니다. 이 값을 allowDefinition="MachineToApplication" 으로 변경하면, 웹 응용 프로그램의 web.config 에서 processModel 섹션을 재정의 할 수 있습니다.

 

다시 원점으로 돌아와서, 세선 관리를 ASP.NET Session State Service 로 지정하는 방법은 굉장히 간단합니다.

다음과 같이 web.config 를 변경하면, 세션 상태 관리를 ASP.NET Session State Service 로 지정할 수 있습니다.

 

<system.web>

       <sessionState mode="StateServer"

                      stateConnectionString="tcpip=localhost:42424">

</sessionState>

 

tcpip address IP 도 가능하며, 도메인으로도 가능합니다. 그리고 뒤의 42424 는 디폴트로 지정된 ASP.NET Session State Service Port 입니다. 만약, 로컬 서버가 아닌 원격 서버의 ASP.NET Session State Service 를 사용하기 위해서는 방화벽의 42424 포트를 허용해 주시면 됩니다.

 

ASP.NET Session State Service 가 디폴트로 사용하는 42424 포트를 변경해 줄 필요도 생길 수 있습니다. 포트를 변경해 주는 UI 는 제공되지 않지만, 레지스트리를 변경하여 포트 번호를 바꿀 수 있습니다.

 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters

 

Port= a5b8 (42424)

 

Port 의 값을 16진수로 대응되는 값을 변경하시고, 반드시 ASP.NET Session State Service 재시작 하시면 변경된 포트를 통해 Service 를 이용할 수 있습니다.

 

간혹, ASP.NET Session State Service 를 시작하였음에도 동작하지 않을 경우, 위의 레지스트리 위치의 값을 확인해 보셔야 합니다.

 

AllowRemoteConnection=1

 

 

값이 1 이 아닌경우, 1로 변경하면 정상적으로 ASP.NET Session State Service 를 이용할 수 있습니다.

 

 

2.    SQL Server

 

SQL Server 를 이용하여 데이터베이스를 통해 세션 상태를 유지합니다. 물리적인 디스크를 통해 I/O 가 일어나므로 가장 느린 상태 관리 방법이지만, 세션의 휘발성의 염려가 없는 가장 안정된 서비스를 제공하는 방법 이기도 합니다.

 

우선, 세션으로 사용할 데이터베이스와 그 외 부수적인(테이블/저장 프로시져) 등을 생성해야 하는데, Visual Studio Command Prompt 를 통해 간단하게 생성할 수 있습니다.

 

 

C:\> aspnet_regsql.exe -S <MachineName> -E -ssadd -sstype p

 

 

[그림2] aspnet_regsql.exe 가 생성한 데이터베이스

 

위 명령을 수행하면 [그림2] 와 같이 ASPState 라는 데이터베이스가 생성이 됩니다. 만약 위의 명령줄에서

 

–sstype p

 

를 제거하면 tempdb 에 테이블이 생성 되어 집니다.

 

ASP.NET 1.1 에서 SQL Server 데이터베이스 생성하는 방법

HOWTO: 영구적으로 SQL Server 세션 상태를 관리할 있도록 ASP.NET 구성

 

데이터베이스가 정상적으로 만들어 졌다면, 다음과 같이 web.config 를 변경하여 상태관리 방법을 변경할 수 있습니다.

 

<system.web>

       <sessionState mode="SQLServer"

                      sqlConnectionString="server=localhost; uid=*******; pwd=********* ">

       </sessionState>

 

이전 포스팅에서 언급한 바, Out-of-process 를 통해 세션 상태를 관리하게 되면 Session 개체에 반드시 Serializable(직렬화할 수 있는) 개체만 Session 에 저장할 수 있습니다.

 

3.    구성 섹션을 암호화

 

본 섹션은 Out-of-process 와 관련이 없는 부분이지만, 경우에 따라 민감할 수 있습니다. 왜냐하면, web.config 를 통해 Server Farm 의 접속 정보가 잠재적으로 노출되어 있기 때문입니다. 서버의 .config 가 기본적으로 System.Web.HttpForbiddenHandler 로 지정되어 있어, 외부의 사용자는 web.config 의 내용을 알 수 없지만, 외부 개발자나 시스템 담당자 외에 공개가 되어서는 안되는 경우 구성 섹션을 암호화 하는 방법이 있습니다.

 

이 부분에 대해서는 아래의 MSDN 링크를 통해 보시면 됩니다.

Web.config 암호화

연습: RSA 컨테이너 만들기 내보내기

 

 

너무 포스팅을 쉬었는지 감을 조금 잃어버렸네요~ ^^; 다음 포스팅은 세션상태를 마이그레이션 하는 방법에 대해 보도록 할 예정입니다. MSSQL 로 제한된 세션 상태를 Oracle 에서 이용하도록 변경이 가능하며, 모든 로직을 감추고 분산처리 하는 방법, 이것을 활용한 SSO 시스템의 전반적인 설계까지 알아보도록 할 예정입니다.



Posted by 땡초 POWERUMC

댓글을 달아 주세요



[.NET/ASP.NET] - 실전 ASP.NET Session [1] - 쿠키를 이용한 상태관리와 위험성
[.NET/ASP.NET] - 실전 ASP.NET Session [2] - 상태관리의 종류
[.NET/ASP.NET] - 실전 ASP.NET Session [3] - 다양한 세션 관리 방법
[.NET/ASP.NET] - 실전 ASP.NET Session [4] - 세션상태 마이그레이션

 

 

이 단원은 ASP.NET 의 어느 책을 보아도 나오는 반드시 나오는 챕터이죠. 그만큼 기본적이고 중요한 부분입니다. 왜냐하면 웹이라는 것은 기본적으로 아무런 상태를 저장할 수 없기 때문입니다. 하지만, ASP.NET 을 이용하면 다양한 방법을 통해 상태를 쉽게 할 수 있고, 쉽게 간과할 수 있는 부분을 다시 한번 되새길 수 있도록 구성해 보았습니다.

 

Application 개체

 

이 프로퍼티는 HttpApplicationState 의 인스턴스 입니다. 대부분의 ASP.NET 의 상태관리 개체는 키(Key) 와 값(Value) 이 쌍을 이루는 컬렉션 형태입니다. 따라서 다음과 같이 사용할 수 있습니다.

 

HttpApplicationState app = HttpContext.Current.Application;

app["Key"] = "Value";

 

Response.Write( app["Key"].ToString() );

 

하지만, Page 클래스에 Application 라는 프로퍼티로 정의가 되어 있기 때문에, 다음과 같이 더 간결하게 사용할 수 있습니다.

 

Application["Key"] = "Value";

 

Application 개체는 웹 응용 프로그램의 전역적인 값을 유지할 수 있습니다. 웹 응용 프로그램의 설정이나 세션에 관계없이 전역적인 변수를 저장하는데 유용합니다. 또한, 프로세서가 실행되고 있는 서버의 메모리에 값을 저장하고 있기 때문에 빠르게 데이터를 엑세스 할 수 있습니다. 웹 응용 프로그램은 Worker Processer 의 가상 디렉토리로써 AppDomain 이라는 응용 프로그램 도메인에 속하게 됩니다. 다시 말하자면, HttpRuntime / HttpApplicationState / Page 개체는 AppDomain 에 격리되고, 그렇기 때문에, Application 개체는 응용 프로그램 간에 값을 공유할 수 없습니다.

 

Application 개체는 프로세서가 실행되는 서버의 메모리에 값을 저장하기 때문에, Application 개체에 저장할 데이터는 작으면 작을 수록 좋습니다.

 

[그림1] IIS ASP.NET 리소스 처리 아키텍쳐

( 위 그림과 더불어 참고 하세요 http://www.windowsdevcenter.com/pub/a/windows/2004/03/02/inside_iis.html )

 

ASP.NET 은 요청에 대해 스레드 풀에 요청을 할당하여 그 스레드로 페이지를 실행시킵니다. 페이지의 수행 주기가 끝나게 되면 해당 스레드는 스레드 풀에 반환되게 됩니다. ASP.NET 에 대한 요청은 이러한 다중 스레드 환경에서 Application 개체의 Input 이 발생하게 되면 동시성에 의해 원치 않는 데이터가 저장될 수 있습니다. 그렇기 때문에, Application 개체의 초기화는 Global.asax Application Start 이벤트를 통해 개체를 초기화 하도록 해야 합니다.

 

만약, Application Start/End 외에 다른 곳에서 Application 에 데이터를 Input 하기 위해 잠금/잠금 해제의 코드를 작성해 주시면 됩니다.

 

Application.Lock();

Application["Count"] = (int)Application["Count"] + 1;

Application.UnLock();

 

위와 같이 다중 스레드에서 Application 개체를 동기화 하기 위해 Lock() UnLock() 메서드를 통해 잠금을 수행할 수 있습니다.

 

Application 사용시

1.      데이터의 크기가 작을 때 경우 이용

2.      메모리에서 I/O 가 발생하기 때문에 잦은 입출력에 용이

3.      잦은 I/O 가 발생하는 페이지에 사용시 반드시 동기화 코드를 작성

 

 

ViewState 개체

 

ViewState 는 현재 페이지의 상태를 유지하기 위해 사용됩니다. 기본적으로 ViewState 히든필드(Hidden Field) 형태로 HTML 로 내려보내지며, 포스트 백이 발생하게 되면 Hidden Field 를 전송하여 페이지 상태를 유지할 수 있습니다.

 

[그림2] 소스 보기를 통해 본 ViewState

 

ViewState 는 계층적인 형태로 표현이 되는데, 자세한 정보는 태요 사이트ViewState 데이터를 분석하자를 참고 하시기 바랍니다.

 

ViewState 는 여느 상태 관리 컬렉션과 같이 간단하게 사용할 수 있습니다.

 

ViewState["CategoryID"] = "1234";

Response.Write(ViewState["CategoryID"].ToString());

 

특별한 기교 없이 쉽게 구현이 가능하지요.

 

ViewState 는 위의 [그림2] HTML 소스를 보는 것과 같이 상태 정보를 HTML 에서 정보를 가지고 있기 때문에 서버의 리소스가 필요하지 않습니다. 서버의 리소스가 필요하지 않기 때문에, 실제로 가장 많이 사용하는 상태 관리 기법이면서, ASP.NET 플랫폼의 서버 컨트롤이나 Form Runat=”Server” ( , Form 의 모든 요소를 서버에서 처리 ) 하기 때문에, 많은 부분의 상태 관리가 자동화 되어 있습니다.

 

이것이 강점이자 단점이 되는 것이 ASP.NET ViewState 입니다. Form 의 상태를 서버에서 처리를 하고, 서버 컨트롤이라는 편리한 ASP.NET 컨트롤을 이용하고, 이 컨트롤들은 스스로 상태 관리 능력을 가지고 있습니다. Control SaveViewState LoadViewState 메서드를 override 함으로써 컨트롤 스스로 상태 관리 능력을 부여할 수 있기 때문입니다. 하지만 GridView DataList 와 같은 많은 기능을 가지고 있는 컨트롤들은 바인딩 되는 DataSource 에 따라 ViewState 는 눈덩이 처럼 불어나게 됩니다. 아무 생각 없이 서버 컨트롤만을 사용하다 보면 ViewState 의 용량만 10 Kb, 20 Kb 가 훌쩍 넘어 버립니다. 우리나라와 같이 네트워크 인프라가 원활한 환경이라면 콧방귀를 뀌고 우습게 여길 수 도 있지만, 모바일 장치나 인터넷 속도가 느린 외국에서 서비스를 해야 할 경우 상황은 달라지게 됩니다.

 

ASP.NET ViewState Serializable 형태여야 합니다. ViewState HTML 코드로 랜더링 하기 위해서 이러한 직렬화 과정을 거치게 되고, 이것을 Base64 인코딩을 통해 [그림2] 와 같이 문자열로 변환하게 됩니다. 포스트백을 통해 Base64 디코딩과 역직렬화 과정을 거쳐 개체로 반환되게 됩니다. 수십/수백명의 동시 접속자가 예상되는 서비스에서는 ViewState 는 곧 CPU 부하를 증가시키고, 서버를 늘릴 수 밖에 없는 비용적인 측면을 생각하면 개인이나 기업에게는 잠재적인 부담일 수 밖에 없습니다.

 

해결책으로 가장 좋은 방법은 ViewState 를 사용하지 않는 것입니다. 대부분의 ViewState 는 서버 컨트롤에서 발생하기 때문에, 서버 컨트롤의 사용량을 줄이는 것이 좋은 방법이 될 수 있습니다. 더 나아가, Form Runat=”Server” 속성을 날려버리고, 여러개의 form 으로 나누어 서버로 전송되는 트래픽의 양을 줄여 ViewState 의 연산을 없애 버리는 것도 좋은 방법입니다.

 

다음 코드는 ViewState 를 사용하지 않는 최적화된 HTML 입니다.

 

<body>

    <form id="form1">

    <div>

             <input id="txtName" name="txtName" type="text" value="<%= txtName %>" />&nbsp;

             <input id="btnSend" name="btnSend" type="submit" value="Send" />

    </div>

    </form>

</body>

 

Form 태그의 Runat=”Server” 속성을 제거해 버렸습니다. 그렇기 때문에, Send 버튼을 클릭하게 되면 txtName 의 텍스트박스는 상태 관리가 되지 않습니다. 포스트백(Submit) 이 발생하면 txtName 의 텍스트박스는 입력된 값이 모두 사라지게 됩니다. Form Runat=”Server” 를 제거하면 기본적인 ViewState 조차 생기지 않습니다.

 

그럼 여기에 상태 관리를 위한 코드를 삽입해 보면..

 

public partial class _Default : PageBase

{

      

       protected string txtName;

 

       protected void Page_Load(object sender, EventArgs e)

       {

             try

             {

                    this.txtName = Request.Params["txtName"];

             }

             catch (Exception ex)

             {

                    Response.Write(ex);

             }

       }

}

 

위 코드를 삽입하게 되면, Send 버튼 클릭 후에도 txtName 의 텍스트박스는 이전에 입력했던 값을 유지하게 됩니다. 서버컨트롤과 자동화된 ASP.NET 의 상태 관리 없이 개발자가 직접 코드를 작성하여 상태 관리를 하는 방법입니다.

 

짧은 제 경험상, 기업의 내부 인트라넷의 경우 다양한 요구사항과 업무 처리를 위해 서버 컨트롤과 더불어 다양한 기능의 상용 컴포넌트와 컨트롤을 사용하기도 합니다. 하지만, 웹 서비스와 같이 불특정 다수에게 노출이 되는 중~대규모 사이트라면 서버컨트롤을 자재하여 사용하기도 하지만 아예 위의 방법처럼 코드를 통해서 상태 관리를 하기도 합니다. 생산성 측면에서 본다면, 당연히 개발 속도는 자동화된 ASP.NET ViewState 를 사용하는 것보다 느릴 수 있지만, 서버로 전송되는 트래픽의 양이 줄고, 서버의 CPU 연산이 줄게 되어 최종 사용자(End User) 측면에서는 빠른 응답 속도를 기대할 수 있습니다.

 

하지만, ViewState 를 포기한다는 것은 굉장히 어려운 문제일 수 있습니다. Form Runat=”Server”를 사용하지 않고, 수동적으로 상태 관리를 하게 되면, 많은 편리한 ASP.NET 의 기능을 사용할 수 없기 때문이죠. 그 대표적인 예가, ASP.NET AJAX ScriptManager 컨트롤 또한 사용할 수 없게 되고, 내부적으로 SaveViewState LoadViewState 를 사용하는 모든 서버 컨트롤(Label 외 몇 가지 컨트롤은 제외…)은 사용할 수 없게 됩니다. 때문에, 적절한 대안을 찾아야 하는데 바로 Control 이란 부모 클래스에는 EnableViewState 속성이 대안이 될 수 있습니다.

 

EnableViewState 는 컨트롤이 ViewState 를 사용할 것인지의 여부를 결정합니다. DataGrid 를 예로 들자면, 기본적인 EnableViewState 속성이 True 이기 때문에, 최초 한번의 DataBind 을 하고, 포스트백이 발생하여도 상태는 유지되지만, DataGrid 의 속성을 EnableViewState=False 로 변경하면, DataSource 의 데이터를 더 이상 ViewState 로 관리하지 않게 됩니다. 모든 컨트롤의 EnableViewState 속성을 변경해야 한다면, Page EnableViewState 도 있습니다. Page EnableViewState 는 더 이상 그 Form 은 모든 ViewState 를 관리 하지 않겠다는 것을 의미하며, 지저분한 ViewState 를 더 이상 생성하지 않습니다. (, 포스트백인지 아닌지의 여부를 감시하는 일부의 Hidden Field 를 생성할 수 있습니다.)

 

위의 내용과 더불어 EnableViewState MAC 이라는 것이 있습니다. 높은 수준의 데이터 무결성이나 데이터의 훼손이 큰 경우 EnableViewStateMac 속성을 True 로 지정해야 합니다. (기본값은 False) 입니다. 지난 아티클의 RewritePath 포스트백(Postback) 문제와 해결 방법을 통해 포스트백의 URL 을 지정해 줄 수 있는데, 이러한 비 정상적인 방법(?) 을 이용하게 되면, 서버 측에서는 ViewState 가 훼손되었거나, 데이터가 변경된 것으로 인지할 수 있습니다. 또한, 가끔씩 UpdatePanel 을 이용해 프로그램을 하다보면 개발자의 잘못된 알고리즘으로 인하여 ViewState 가 손실되거나 변경되는 경우가 있습니다. 이러한 경우들에 대해 EnableViewStateMac=False 를 통해 데이터의 무결성 검사 등의 ViewState 의 상태를 검사하지 않을 수 있게 됩니다.

 

하지만, 결정적으로 ViewState 는 클라이언트에 내려지고, 다시 이 ViewState 는 서버로 전송되는 형태이기 때문에, ViewState 는 언제든지 조작의 가능성이 있습니다. 간단한 툴을 이용해서 ViewState 가 어떤 정보를 가지고 있는지 쉽게 알 수 있기 때문에, ViewState 의 데이터를 항상 신뢰하면 안됩니다. ASP.NET 2.0 부터 이러한 ViewState 를 암호화 할 수 있는 기능이 제공됩니다. 간단히 @Page 지시자에 ViewStateEncryptionMode="Always" 를 넣음으로써 페이지의 ViewState 를 암호화 할 수 있습니다. 모든 웹 응용 프로그램의 페이지에 이러한 속성을 주기 위해서 web.config system.web 섹션에

 

<system.web>

       <pages viewStateEncryptionMode="Always">

 

로 지정해 주기만 하면 됩니다.

 

이것의 기본 속성은 Auto 입니다. Auto 는 아무런 ViewState 암호화가 수행되지 않습니다. , 개발자의 코드를 통해 페이지의 암호화를 지정해 줄 수 있는데,

 

Page.RegisterRequiresViewStateEncryption();

 

의 명시적인 호출로 Auto 상태에서 ViewState 암호화를 지정해 줄 수 있습니다. , 암호화를 수행하게 되면 그만큼의 CPU 의 부하도 늘게 된다는 것 또한 잊어서는 안됩니다.

 

ViewSate 사용시

1.      프로젝트가 어떤 용도의 서비스인지 잘 판단하여, 상태관리를 자동화 할 것인지, 수동화 할 것인지 결정

2.      EnableViewState MAC / ViewStateEncryptionMode 유용할 있지만, 남용하게 되면 CPU 파워만 증가시킨다

 

 

Sessoin 개체

 

이제야 Session 개체가 나왔습니다. 3회차부터 이 Session 개체를 더 자세히 볼 예정이기 때문에, 여기에서의 Session 은 가볍게 보도록 하겠습니다.

 

여느 상태관리 개체와 마찬가지로 사용법은 매우 간단합니다.

 

Session.Add("Key1","Value1");

                          

Session["Key2"] = "Value2";

 

 [그림3] 브라우져 요청 별 쿠키 생성

 

기본적으로 HTTP 프로토콜을 이용한 요청은 독립적으로 요청을 처리합니다. 요청에 대해 상태를 유지할 수 없기 때문에 어떤 사용자에 의한 요청인지 알 수 있는 방법이 없습니다. 그렇기 때문에, Session 은 쿠키와 브라우져에 의존적입니다. 이전 포스팅의 실전 ASP.NET Session [1] - 쿠키를 이용한 상태관리와 위험성에서도 설명했듯이, 브라우져가 사이트에 요청을 할 때 생성이 되고, 브라우져를 닫게 되면 쿠키도 삭제된다고 하였습니다. , 만료시간이 지정된 경우에 텍스트 형태로 사용자의 하드 디스크에 저장이 됩니다.

 

[그림4] 브라우져별 요청에 대한 쿠키 생성

 

브라우져는 의해 처음 사이트에 요청을 하게 되면 쿠키를 생성하게 됩니다. 때문에 브라우져 별로 처음으로 사이트를 요청하게 되면 쿠키를 생성하는데 그것이 바로 ASP.NET 이 상태관리를 위한 Session ID 를 쿠키로 생성하게 됩니다. 이러한 행위는 사용자별로 고유한 세션 저장소에 정보를 저장하기 위해서이며, 사이트에 접속한 여러 사용자를 구별한 수 있는 키(Key) 가 되기도 합니다. 좀 더 정확하게 말하면, 이러한 Session ID AppDomain 별로 웹 응용프로그램의 Application ID 가 세션의 키값으로 사용되어 집니다. 그렇게 때문에, AppDomain 간의 Session 의 값은 공유할 수 없게 됩니다.

 

ASP.NET Session 은 기본적으로 쿠키를 통해 Session ID 를 인식합니다. 하지만, 특정 사용자의 브라우져 상태는 쿠키를 허용하지 않을 수 도 있습니다. 또한 사용자 임의로 쿠키를 허용하지 않도록 할 수 도 있습니다. ASP.NET 은 쿠키 기반의 세션 유지가 아닌 쿼리 문자열을 통해 Session ID 를 유지할 수 도 있습니다.

 

system.web 섹셕의 sessionState 요소를 통해 아래와 같이 cookieless true 로 설정하면, 더 이상 쿠키 기반으로 Session ID 를 유지하지 않고, 쿼리 문자열을 통해 Session ID 를 식별하게 됩니다.

 

<system.web>

       <sessionState cookieless="true"></sessionState>

 

[그림5] 쿼리 문자열을 통한 Session ID 유지

 

ASP.NET 은 보다 자동화된 Session ID 를 유지하도록 해 줍니다. 가령, 사용자의 브라우져나 쿠키를 허용하지 않거나, 임의로 쿠키를 허용하지 않도록 설정한 경우 cookieless AutoDetect 로 설정하게 되면, ASP.NET 쿠키를 통해 Session ID 를 유지할 것인지, 쿼리 문자열로 Session ID 를 유지할 것인지를 스스로 결정하게 됩니다. 기본적으로 cookieless 속성은 디폴트로 UseCookies 로 설정되어 있기 때문에,

 

<sessionState cookieless="AutoDetect"></sessionState>

 

과 같이 개발자가 직접 수정해 주셔야 합니다.

 

기본적은 Session 설정은 In-of-process 를 통해 처리되게 됩니다. In-of-process ASP.NET Worker Process 를 통해 처리가 되며, Worker Process 가 구동되는 서버의 메모리에 저장이 됩니다. Application 개체와 마찬가지로 Session 개체는 In-of-Process 로 처리가 될 경우 웹 응용프로그램의 빌드나 web.config 의 설정이 바뀔 경우 개체의 데이터는 모두 잃어버릴 수 있습니다. 명시적으로 sessionState mode 속성을 지정하지 않으면 디폴트로 InProc 입니다

 

Session 을 잃게 된다는 것은 웹 서비스에게 치명적일 수 있습니다. 한가지 예를 들어보면, 쇼핑몰 사이트가 있습니다. 상품을 결재를 하기 위해서 대부분 여러 단계를 거쳐 인증을 하고, 결재가 진행됩니다. 하지만, 사이트 관리자는 서버의 치명적인 버그를 발견하여 수정하고 웹 어플케이션을 업데이트 한다면, 결재중인 사용자의 Session 은 끊겨버리게 됩니다. 물론, 이러한 결재 오류에 대한 정책이 잘 정립 되어 운영 된다면 문제가 없겠지만, 데이터베이스를 조작하지 못하는 서버 관리자만을 채용하는 소형 쇼핑몰일 경우 이러한 문제는 쉬쉬 하며 넘어갈 일만은 아닐 것입니다.

 

[그림6] 다양한 Session State 유지 계획

 

하지만, 여기서도 문제라면 서버의 메모리는 제한적입니다. 때문에 In-of-process 가 아닌, Out-of-process 로 전환할 필요가 생기게 됩니다. 또한 대형 사이트일 경우 Web Farm 으로 부터 여러 개의 Server Farm 을 구성하여 분산처리 할 경우 보다 나은 성능과 관리의 이점이 있습니다. , 한가지 Out-of-process Serializable 개체만을 Session 에 저장할 수 있다는 제한이 있습니다.(당연하겠지만)

 

다음 아티클에서 Session State 를 유지하는 다양한 방법을 알아 보고, 방법에 대한 보다 나은 이슈를 제시할 예정입니다.

 

용어설명 참고

위키디피아 Web Farm(Server Farm)

Web Garden – MSDN ASP.NET 작업자 프로세스

 

Session 사용시

1.      소규모 사이트일 경우 In-of-process 를 권장

2.      Session 은 어떻게 사용 하느냐도 중요하지만 어떻게 운영하는가에 대한 기술적인 계획이 필요

 


Posted by 땡초 POWERUMC

댓글을 달아 주세요

[.NET/ASP.NET] - 실전 ASP.NET Session [1] - 쿠키를 이용한 상태관리와 위험성
[.NET/ASP.NET] - 실전 ASP.NET Session [2] - 상태관리의 종류
[.NET/ASP.NET] - 실전 ASP.NET Session [3] - 다양한 세션 관리 방법
[.NET/ASP.NET] - 실전 ASP.NET Session [4] - 세션상태 마이그레이션


프로그램의 코드를 짜면서 쉽게 간과할 수 있는 상태관리 오류 등을 범하기도 합니다. 때론, 적절한 상태 저장소를 잘못 선택하여 잘못된 코드와 결과를 보는 경우도 있습니다. 이번 아티클은 그런 오류를 범하지 않고, 적절한 상태관리를 할 수 있도록 방법을 제시해 줄 예정입니다. 또한, 3회 포스팅 부터는 그저 별 것 아닌 Session 이라는 상태 저장소에 대해 좀 더 깊이 알아보고, Session 을 확장하는 방법과 시스템의 전반적인 설계 방법까지 알아볼 예정입니다.

 

1.    쿠키를 이용한 상태관리와 위험성

 

ASP.NET 플랫폼은 다양한 상태관리 방법을 제공해 줍니다. 그렇기 때문에, 개발자는 적절한 상태관리 저장소를 선택하기만 하면 됩니다. 그럼, 상태 관리의 종류와 이것을 선택하기 위한 조건, 적절한 사용 방법도 함께 살펴보겠습니다.

 

1.1.    쿠키 (Cookie) 를 이용한 상태관리

 

우선 쿠키의 생성 주기를 보면, 쿠키는 브라우져가 사이트에 요청을 할 때 생성이 됩니다. 그리고 브라우져를 닫으면 쿠키는 삭제됩니다. 이러한 쿠키에 대한 정보를 브라우져가 관리하게 되지만, 웹 서버에서 쿠키에 대한 만료시간 정보를 보낼 경우, 브라우져는 쿠키 정보를 하드 디스크에 저장하게 됩니다. 그리고 만료된 쿠키는 쿠키를 작성한 웹사이트를 방문할 때 브라우져가 삭제하게 됩니다.

 

[그림1] 자바스크립트로 네이버의 쿠키 정보 확인

 

쿠키는 브라우져마다 다를 수 있지만 최대 4096 Bytes(4KB) 의 텍스트 정보를 저장할 수 있고, 최대 300개의 쿠키를 보존하며, 사이트마다 20개의 쿠키를 허용 합니다. 용량과 개수의 제한이 있기 때문에, 경량의 정보를 저장할 때 유용합니다. 하지만, 쿠키의 만료시간을 지정하게 되면 텍스트로 사용자의 하드디스크에 정보를 저장하기 때문에 메모장을 이용하여 임의로 쿠키 정보를 변경할 수 있습니다. 심지어, 자바스크립트를 이용하여 쿠키를 조작할 수도 있기 때문에, 중요한 정보를 쿠키에 저장하는 것은 매우 부적합 합니다.

 

쇼핑몰의 최근 본 상품과 같이 서버에서 정보를 관리할 필요가 없고, 정보의 보안이 필요 없고, 가벼운 경우 선택하면 됩니다.

 

쿠키는 다음의 경우에 사용되면 됩니다.

1.      1회성 정보일 경우

2.      작은 양의 정보를 저장할 경우

3.      중요하지 않은 정보일 경우 (개인정보나 노출되면 안되는 정보는 절대로 쿠키를 사용하지 마십시오)

 

 

1.2.    쿠키의 위험성

 

위의 사용 조건이 충족 되었더라고 하더라도, 안심하면 안됩니다. 쿠키의 정보가 조작이 되었을 경우, 쿠키 정보를 절대로 신뢰하면 안되기 때문입니다.

 

다음과 같이 쿠키의 정보가 변경되었을 경우를 생각해 봅시다.

 

protected void Page_Load(object sender, EventArgs e)

{

   Response.Cookies["USERID"].Value = "<script>alert('a');</script>";

   Response.Write("USERID" + Request.Cookies["USERID"].Value );

}

 

위의 샘플은 특별한 오류가 없습니다. 하지만, 의도하지 않은 자바스크립트 코드가 실행됩니다.

 

그럼 좀 더 나쁜 상황을 만들어 보면,

 

Response.Cookies["USERID"].Value = "<script>location.href='http://피싱사이트.com';</script>";

 

Response.Cookies["USERID"].Value = "<script><iframe src='http://a.com/악성코드페이지.htm' /></script>";

 

물론, 위의 샘플은 프로그램적으로 잘못된 정보를 입력하였지만, PC 방과 같은 불특정 다수가 사용하는 곳의 PC 일 경우, 쿠키를 조작하여 피싱 사이트로 유도하여 사용자 아이디/비밀번호를 빼내거나, 중요한 개인 정보를 빼낼 수 있습니다. 또한, 악성코드가 포함된 페이지를 포함시킬 수 도 있습니다.

 

이럴 경우,

 

Response.Write( Server.HtmlEncode(Request.Cookies["USERID"].Value) );

 

같이 HTML Encoding 통해 쿠키를 스크립트로 조작하는 악의적인 조작을 피할 있습니다.

 

최근 옥션의 해킹 사건 이후, 2차 또는 3차 해킹 피해 까지 일어나고 있습니다. 이러한 최악의 시나리오가 내가 만든 코드에서 발생하였다면 얼마나 개발자로써 치욕적일까요? 빠듯한 일정이라도, 이러한 잔지식(?)을 오늘 알게 되었다면, 꼭 적용하시기 바랍니다.

 

[그림2] 양날의 검과 같은 쿠키 [ 사진출저는 여기 ]

 

위의 몇 가지 예시에서 본 것과 같이 쿠키는 매우 유용하면서도 위험할 수 있는 양날의 칼과 같습니다. 바로 쿠키의 값을 클라이언트 PC 에서 유지되고 있기 때문에, 의도되지 않은 다양한 조작이 이루어 질 수 있는 것입니다.

 

더 자세한 내용은 MSDN 내용을 참고하시면 사용방법이 잘 나와있기 때문에 MSDN 을 참고하시면 될 것 같습니다.

 

위키피디아 http://en.wikipedia.org/wiki/Http_cookie

MSDN - http://msdn.microsoft.com/ko-kr/library/ms178194(VS.80).aspx

 

Posted by 땡초 POWERUMC

댓글을 달아 주세요