[ Architecture, Technology ,Web ] SSO(Single Sign On) 그리고 SAML에 대해

이미지
이번 프로젝트 내부에서 어쩌다보니  유저 인증 관련 업무를 담당하게 되었고, 해야하는 업무는 내부에 사용했던 적이 없던  새로운 개발 플랫폼에서  SSO의 프로토콜 중  SAML을 이용해 앱의 인증을 구현해야만 했다. SSO를 생각해본적 조차 없는 상황에 이를 새로운 개발 플랫폼에 도입해야 했기 때문에 많은 시행착오를 겪었으나 구현에 성공하였으며 덕분에 SSO에 대한 전반적인 지식을 쌓을 수 있었다. 이번에는 그러한 과정에서 나온 지식들과 경험을  공유하고자 한다. SSO에 대한 정의 먼저 사전적 정의 부터 살펴보자. 다만, 기술적인 용어다보니 자주 사용하는 옥스포드 사전에 정의를 찾을 수 없기 때문에  검색으로 찾을 수 있는 정의를 몇 가지 살펴보고 교차 검증을 해보자. 첫 번째 정의를 살펴보자. Single sign-on (SSO) is an identification method that enables users to log in to multiple applications and websites with one set of credentials.  SSO는 웹사이트에서 한 번의 인증(one set of credentials)으로 복수의 어플리케이션에 로그인 할 수 있는 인증(identification) 방법(method) 이다. 두 번째는 위키피디아의 정의이다. Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems. SSO는 독립적이지만 연관되어있는 몇몇 소프트웨어에 대해 하나의 ID로 로그인을 할 수 있도록 하는 인증 구조(scheme) 세부 설명에 조금 차이가 있어 보이지만 전체적인 틀은 매우 비슷해 보인다.  몇 가지 포인트가 되는 단어를 추출해 이를 연결해보자면 아래와 같은 의미를 산출 할 수 있다. 독립적이지만 연관되어 있

[ Essay - Technolgy, IT, Architecture, Algorithm ] C계열과 JAVA에서의 힙 영역(Heap Area)과 스택 영역(Stack Area)에 대해서


생각할 수록 C계열과 JAVA라는 언어는 
비교해 살펴보기에 매우 적절한 언어라고 생각 한다.

재미있게도 말이다.

이에 대해서는 아마 최근의 핵심적인 패러다임을 
담고 있기 때문이라고 생각하고 있다.

최근 객체지향 패러다임을 담고 있는 언어들은 
JAVA의 영향을 받지 않은 언어를 찾기는 쉽지않으며 

구조적 패러다임을 담고 있는 언어들 또한
C계열의 영향을 받지 않은 언어를 찾기란 쉽지 않다.

후에 이 두 가지 언어에 대해 
어떻게 평가를 내릴지는 알 수 없으나

현재로서는 이 두 가지 언어가 프로그래밍 역사에서 한 획을 그을 수 있는 
언어들이라고 개인적으로는 생각 하고 있다.

이번에는 C계열과 JAVA의 빌드 과정에 대한 이야기에 이어서
C계열과 JAVA의 힙 영역(Heap Area)과 스택 영역(Stack Area)에 대해 이야기해보자.

힙 영역(Heap Area)과 스택 영역(Stack Area)에 대해서


힙 영역과 스택 영역에 대한 이해는 
개발자로서 알아둬야할 것 중에 하나이지 않나 싶다.

물론 필수라고 할 수 는 없지만,
진정한 엔지니어나 아키텍터라면 이해해야만 하는 덕목이라고 생각 한다.

왜냐하면, 언어 내부에서 힙 영역과 스택 영역을 
어떻게 다루느냐에 따라 퍼포먼스가 갈릴 수 있기 때문이다.

따라서 시스템의 퍼포먼스 까지 생각해야하는 
진정한 엔지니어나 아키텍터가 이에 대한 이해가 없다면
훌륭한 엔지니어나 아키텍터가 될 수 없을 것이다.

어쨋든 먼저 C계열과 JAVA를 비교하기전에
힙 영역과 스택 영역이 무엇인지에 대해 간단히게 이야기 해본 후에
실제 C계열 중 C#과 JAVA에서 메모리 관리를 하기 위해 
어떻게 이 스택과 힙이라는 영역을 활용하는지에 대해 이야기 해보자.

 스택 영역(Stack Area)에 대해


스택이라는 단어에 대해서는 
대학에서 컴퓨터 관련 학과를 나오는 사람에게는 매우 익숙한 단어 일 것이다.

설사 그렇지 않더라도 스택이라는 단어는 
우리 주변에서 쉽게 사용되어지고 있기 때문에
단어 자체는 익숙 할 것이다.

스택은 데이터들을 어떻게 보관할 것인지에 대한 많은 솔루션들 중 하나이다.
익히 자료구조(Data Struct)라고 불리우는 것들이다.




간단하게 말해서 먼저 들어간 데이터는 맨 나중에 빠지고
가장 최근에 들어간 것부터 순서대로 나오는 방식의 자료구조 이다.

일반적으로 쉽게 설명하기 위해
LIFO(Last In First Out)이라고 불리우며, 
한국에서는 선입후출이라고 불리우기도 한다.

스택의 장점은 너무나도 명확하다.

로직 자체가 간단하기 때문에 구현이 너무나도 쉬우며
퍼포먼스도 뛰어난 편이다.

때문에 스택은 꽤나 많은 곳에서 자주 활용되며
구현이 쉽고 빠르기 때문에 메모리 관리 영역에서도 활용되어지고 있다.

C언어의 경우 일반적으로 스택 방식으로 구현된다고 한다.

일반적으로 스택의 크기는 고정되어 지기 때문에
스택의 크기보다 길이가 길 경우 오버플로우 라는 에러가 나타난다.

이해하기 어렵다면, 
스택은 일반적으로 정적 할당을 위해 사용되며,
구현이 쉽고 빠르다는 것만 기억하면 충분하다고 생각 된다.

힙 영역(Heap Area)에 대해


힙 영역은 스택보다는 아니겠지만,
컴퓨터 관련 학과를 나왔다면 익숙하지 않더라도 얼핏 들어는 봤을 것이다.

왜냐하면 자료 구조(Data Sturct)에 있어서 스택 뿐만아니라
힙 또한 빠질래야 빠질수 없는 자료 구조이기 때문이다. 

힙은 우선 순위 큐(Priority Queue)라는
추상적인 데이터 유형(Abstract Data Type)을 최대한 효율적으로 구현한 것을 말한다.


위의 사진과 같이 힙은
힙의 내부의 데이터들을 각 우선 순위를 가지고 있어,
가장 높은 혹은 가장 낮은 순위를 가진 데이터를 루트로서 
트리 형식으로 저장되어지는 자료 구조 이다.

물론 실제 컴퓨터가 위의 트리 구조를 이해하고 구현되는 것은 아니고
실제로는 일반적으로 배열로 구현 된다.

그렇기에 힙을 추상적인 데이터 타입(Abstract Data Type)이라고 부른다.

힙은 우선 순위가 가장 높은 혹은 낮은 순위의 데이터(객체)를 
반복적으로 제거해야할 때 매우 유용한 데이터 구조 이기 때문에

각 프로세스를 처리해야 하는 순서를 정해야 하는 
메모리 관리 기법에 딱 적합한 자료구조이다.

이해하기 어렵다면
힙은 일반적으로 동적 할당을 위해 사용되며 
구현이 스택에 비해 어려우며, 느리다는 것만 기억하면 된다고 생각 한다.

JAVA의 힙 영역과 스택 영역에 대해서


먼저 21세기 들어서 아마 가장 인기가 꾸준히 많은
언어 중에 손꼽히는 JAVA를 먼저 이야기 해보자.

JAVA의 힙 영역과 스택 영역은 JVM에서 운용되기 때문에 
이전 포스팅한 글을 먼저 참고하는 것이 좋을 것이라 생각 된다.

가능하다면 아래의 링크를 먼저 참고 한 후에 읽기를 권하지만,
이에 대한 적절한 직감을 가지고 있다면
딱히 읽을 필요는 없을 것으로 생각 된다.

①JAVA에서의 스택 영역 혹은 스택 메모리


어쨋든 먼저 JAVA에서 스택은 
정적 메모리 할당 및 스레드 실행에 사용 된다.

위에서 설명한바와 같이 LIFO의 순서를 가지며,
새로운 메소드가 호출될 때 마다 스택 상단에 
기본적인 변수 및 객체에 대한 참조와 같은 값들이 포함되어 있는
하나의 블록을 생성하게 되며,
메소드가 실행되는 동안에만 스택 영역에 머물게 된다.

메소드의 실행이 끝나면 해당 스택이 자동적으로 제거되며, 
다음 메소드에 공간으로서 활용될 수 있다. 

위에서 기본적인 스택에서 설명한 것과 마찬가지로
공통적으로 (힙에 비해)빠르며, 안전하지만
정적으로 할당된 메모리를 초과한다면 JVM은
java.lang.StackOverFlowError를 반환 한다.


②JAVA에서의 힙 영역 혹은 힙 메모리


JAVA에서 힙 영역은 Java객체 및 
JRE(Java Runtime Environment) 클래스에 대한 동적 메모리 할당에 사용 된다.

여기서 JRE는 Java로 작성되어진 프로그램을 어느 환경(OS와 같은)에서도 
실행하기 위한 라이브러리 파일등이 들어있는 
하나의 도구라고 생각하면 편할 것 이다.

JAVA의 힙 영역의 크기에 제한은 없으며
애플리케이션이 실행되는 동안 힙 영역에 존재하기 때문에
스택과는 다르게 좀 더 복잡한 메모리 관리 기법이 필요하기 때문에 
비교적 느리다.

따라서 JAVA에서는 이 힙 영역을 
Young Generation, Old or Tenured Generation ,
Permanent Generation으로 3가지로 더 작게 나누어서 구분하고 있는데        
이에 대한 자세한 설명은 아래와 같다.

1) Young Generation:

모든 새로운 객체(메소드 등과 같은)가 할당되고 해제되는 곳으로
할당된 이상으로 가득찬다면 가비지 컬렉션(Garbage Collection)이 실행 된다.

2) Old or Tenured Generation: 

Young Generation 공간에서 오랫동안 지속된 객체들이 이동되는 곳으로
Young Generation에 저장될 때 객체에 대한 임계값(Threshold Value)이 설정되는데
이 임계값이 초과 될 경우 Old or Tenured Generation으로 이동 된다.

3) Permanent Generation:

환경(OS와 같은)에 구애받지 않고 Java 프로그램을 실행하기 위한 
런타임 클래스, 메소드 들이 포함되어 있는 
JVM 메타 데이터(meta data) 가 할당되는 영역이다.


여기서 한 가지 주의 해야할 점은 
이 힙 영역에 있는 것들은 전역으로 액세스 권한이 있기 때문에

즉, 다른 프로그램에서도 
이 힙 영역의 할당된 데이터들을 엑세스 할 수 있기 때문에 
보안의 문제가 될 수 있음을 명확히 이해해야 한다.

그렇기에 현재 JAVA를 포함한 여러 언어들을 보면 
전역 변수는 사실상 사용이 금지되어 있는데

그 이유에 대해서 가장 큰 문제로는 여러 프로세스에서 
공통으로 전역 변수를 사용하다보면 
예상치 못한 버그가 나기 때문이기도 하지만,

이 보다 더 중요한 것은 
바로 다른 프로그램 악의적인 프로그램에서 
이 영역에 있는 메모리를 볼 수 있고
메모리 조작이 가능하기 때문이다.

비교적 최근에 한국의 어떤 모바일 게임에서 치오트메틱이라는
단순한 메모리를 찾고 조작 할 수 있는 툴로 
게임 내의 중요한 값들을 조작했던 사건이 있었는데

이는 아마 해당 개발자가 무슨 이유에서인가는 알 수는 없지만,
해당 값을 전역 변수에 저장해 사용한 것으로 추측 해볼 수 있다.

아마 객체 반환에 대한 정확한 이해가 부족했던 것으로 보인다.

또한 실제 Heap Inspection(힙 검사)라는 보안 이슈가 존재하는데,
이는 JAVA 개발자들이 잘 신경쓰지 못하는 부분이기도 하다.

힙 검사는 프로세스의 힙을 덤프 파일로 추출해
데이터를 검사하는 방법으로 경우에 따라서는 심각한 보안 문제가 될 수 있다.

하지만, 이를 완전히 방지하는 것은 불가능하기에
패스워드와 같은 중요한 데이터들은 암호화 처리를 거치기를 권장 한다.



위의 사진은 JAVA에서 스택 영역과 힙 영역이
어떻게 움직이는지를 확인할 수 있는데

역시 객체 지향적 언어 답게
모든 것이 객체로 이루어지는 것을 확인해 볼 수 있다.

C#의 힙 영역과 스택 영역에 대해서


위에서 설명한 것과 같이 JAVA는 객체지향적 언어 답게
가능한 모든 것을 객체화해 관리하려고 하는 부분이 인상 깊다.

그렇다면 이이서 C#의 힙 영역과 스택 영역에 대해서 살펴보자.

기본적으로 JAVA의 스택 영역과 힙 영역와 비슷하다고 보면된다.

정적 할당이 필요할 때는 스택을 사용하며,
동적 할당이 필요할 경우는 힙을 사용한다.

하지만, 
재미 있게도 C#은 JAVA와 조금 다른 관리 기법을 사용하는데 
먼저 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void Method1(){
 
    int x = 1;
    int y = 2;
 
    Calculator cal1 = new Calculator();
 
 
}
 
public void Method2(){
 
    int x = 3;
 
    object i = x;
    int y = x;
 
 
}
cs

위와 같은 Method1과 Method2가 있다고 가정해보자.

먼저 Method1을 살펴보자.

1
2
3
4
5
6
public void Method1(){
    int x = 1;
    int y = 2;
    Calculator cal1 = new Calculator();
 
}
cs

기본적으로 C#은 2번째 코드와 3번째 코드는 스택 영역에 쌓이게 된다.



2번째 코드가 먼저 실행됐으니,
순서적으로는 위와 같이 2번째 위에 3번째 코드가 쌓이게 될 것이다.

그렇다면 4번째 코드는 어디에 쌓이게 될까?

지금까지 내용을 바탕으로 예상해보자면

위의 사진과 같이
힙 영역에 쌓일 가능성이 높아 보인다.

하지만 예상과 다르게 4번째 코드는 스택으로서 쌓인다.

물론 직접 코드가 담겨있는 것이 아닌 

위와 같이 스택에는 해당 객체의 주소값을 담고 있는 포인터 값으로 존재한다.

따라서 4번째 코드와 같은 객체들은
실제 힙 영역에서 존재하고 관리하게 되며
실행 순서는 포인터로서 스택에 저장 된다고 할 수 있다.

그렇기 때문에 스택으로서 해제될 때는
단순히 담겨 있는 주소 값이 삭제되기 때문에 

힙 영역에서의 객체는 그대로 남겨져 있게 된다.

따라서 간단한 이 3줄의 코드 실행 순서는 스택에 의해 관리되며,
스택에는 4번째줄, 3번째줄, 2번째줄이 저장되게 된다.

다만, 4번째 줄의 데이터안에는 포인터 값만 존재하며
실제 객체는 힙 영역에서 관리 한다.

이 Method1라는 메소드가 실행이 완료 되면
스택에 할당되어 있던 대로 차례대로 
4번째 코드,3번째 코드, 2번째 코드가 해제 되지만,

4번째 코드는 포인터가 데이터 값으로 들어 있으므로
실제 힙 영역에 존재하고 있는 Calculaotr라는 메소드는 메모리가 해제되지 않는다.

이는 후에 가비지 컬렉터에 의해 해제 된다.

이 외에 스택이 힙 보다 비교적 빠르고 안전하다는 것은 동일하며
다른 점이라고 한다면 힙 영역에 존재하는 것 또한 
포인터로서 스택에 관리되는 것 정도일 것이다.

다음으로 Method2를 살펴보기 앞서서 
이해를 좀 더 편하게 하기 위해 Method2를 
각각 Method3과 4으로 나누겠다.

먼저 Method3의 코드는 다음과 같다.

1
2
3
4
5
6
public void Method3(){
    
    int x = 3;
    
    int y = x;
}
cs

3번째 코드에서 x를 정의했고, 
5번째 코드에서 y를 정의하고, y의 값을 x를 참조하게 끔 구성했다.

이 메소드의 경우 5번 째 줄은 x의 값을 참조하지만,
재미있게도 C#에서 5번째 줄은 힙이 아닌 스택에서 관리하게 된다.

하지만 이렇게 선언된 이 후 이 두 가지 x와 y값은 별개로서 존재 한다.

즉, 선언 이후에는 x값을 바꾼다고 하더라도, y값이 변하지 않는다는 말이다.

예컨데, 위의 코드의 경우 
각각 x와 y의 값은 3이 값으로서 저장될 것이다.

하지만 이 후 x를 4로 바꾸는 코드를 넣고
이를 실제 실행해 메모리에서 해제된다하더라도

y가 4로 변하지는 않는다는 이야기 이다.

이는 단순히 처음 변수 y를 정의할 때 변수 x자체가 아닌 
안에 담겨 있는 값인 3만을 가져 온 것이기 때문에
이 후에 변수 x 값을 바꾼다 하더라도 변수 y의 값이 변하지 않는다.

이는 C 계열 언어에서 자주 나타나는 
값에 의한 호출(Call by Value)와 동일한 개념이다.

이를 C#에서는 값 유형(Value Type)이라고 부르며
이와 반대의 경우는 참조 유형(Reference Types)이라 부른다.

참조 유형 또한 참조에 의한 호출(Call by reference)과 동일한 개념이다.

이어서 Method4을 살펴보자.

1
2
3
4
5
6
public void Method4(){
 
    int x = 3;
 
    object i = x;
}
cs

그렇다면 위와 같이 3번째 줄과 5번째 줄의 유형이 다르면 어떻게 될까?

이 경우 C#에서는 자동적으로 값 유형(Value Type) 3의 값을
참조 유형(Reference Types)으로 바꾸어서 변수 i에 저장해 준다.

왜냐하면, 이런 처리를 해주지 않으면 당연히 
타입이 맞지 않아 컴파일러 선에서 걸러지기 때문이다.

이를 Boxing이라고 하며,

이에 반대인 경우를 UnBoxing이라고 한다.

쉽게 말해서 C#에서는 이를 자동적으로
형 변환(Type Conversion)을 해준다고 이해하면 편할 것이다.

하지만, JAVA에서 String 관련, 
특히 값과 값을 이을 때(+ 구문으로) 성능 저하가 발생하듯이
자동적으로 형변환 해주는 것들은
이것이 반복되면 반복될 수록 엄청난 퍼포먼스의 저하를 발생 시킨다.

동일하게 C#에서 이런 Boxing, UnBoxing 또한
퍼포먼스가 저하되기 때문에 금지까지는 아니지만
가급적 사용하지 않는 것을 권장 한다.

이에 대한 비교는 딱히 내가 하지 않아도
다른 사이트에서 비교를 해놓았기 때문에 아래의 링크를 참고하길 바란다.

위의 사이트에서는 직접 실행해 Boxing이 UnBoxing보다 
큰 성능 저하를 나타내는 것을 검증한 것을 보여 주며,

여기서도 또한 
가능한 Boxing과 Unboxing을 사용하지 않도록 권장하고 있다.

결론


여기까지 해서 스택과 힙에 대한 기본적인 설명과
JAVA와 C계열인 C#에서 어떻게 이 둘을 활용하는지에 대해 살펴보았다.

개인적으로는 모든 것을 객체화 하려하는 JAVA보다는
C#의 경우가 스택과 힙에 대한 사용이 좀 더 퍼포먼스가 좋지 않나 싶다.

엔터프라이즈 급 시스템을 동일하게
두 언어로 개발한다면 C#의 경우가 눈에 띄게 좋지 않을까 싶다.

물론 그렇다고 하더라도
여러 글에서도 이야기 했듯이
무조건 C#으로 개발하는 것이 좋다고는 할 수 없다.

왜냐하면 모든 것은 상황에 맞게 조절해야하며,
이는 아키텍터의 몫이기 때문이다.

아무리 C#이 좋다고 한들,
비교적 뛰어난 C# 엔지니어들이 JAVA 엔지니어보다 
부족하다면, 당연히 JAVA쪽을 선택하는 것이 훌륭한 아키텍터라고 생각 한다.

왜냐하면 익히 알고 있듯이 클라이언트들에게 
이런 퍼포먼스 문제에 그 다지 관심이 없기 때문이다.

그들에게 있어서 중요한 관심 대상은
얼마나 빠른가, 얼마나 버그가 없는 좋은 품질인가, 최신 기술인가 보다는
정해진 예산내에 적절한 품질의 제품을 개발해 주길 원하기 때문이다.

이런 것들에 대한 포인트를 정확히 잡는 것이
아키텍터가 해야 할 과제가 아닌가 싶다.

이 블로그의 인기 게시물

[ Web ] 웹 애플리케이션 아키텍처 (Web Application Architecture)

[ Web ] 서버 사이드(Sever Side) ? 클라이언트 사이드(Client Side)? 1 [서론, 클라이언트 사이드(Client Side)]

[ Web ] 웹 애플리케이션? 웹 사이트?(Web Application? Web Site?)