SAML

SAML(Security Assertion Markup Language)은 기업과 조직에서 단일 로그인(SSO, Single Sign-On)과 신원 연합(Identity Federation)을 구현하기 위한 XML 기반 표준 프레임워크이다. 이 강력한 인증 기술은 현대 기업 환경에서 사용자 인증 및 권한 관리를 간소화하는 핵심 요소로 자리 잡았다.

SAML의 기본 개념

SAML은 2002년 OASIS(Organization for the Advancement of Structured Information Standards)에 의해 처음 표준화되었으며, 현재는 SAML 2.0 버전(2005년 발표)이 가장 널리 사용되고 있다.

이 프로토콜의 주요 목적은 다음과 같다:

  1. 단일 로그인(SSO) 구현: 사용자가 한 번만 인증하여 여러 애플리케이션에 접근할 수 있도록 한다.
  2. 신원 정보 교환: 신원 공급자(IdP)와 서비스 공급자(SP) 간에 인증 정보를 안전하게 교환한다.
  3. 권한 부여 결정: 사용자의 접근 권한에 대한 정보를 전달한다.

SAML의 주요 구성 요소

SAML 생태계는 다음 세 가지 주요 구성 요소로 이루어져 있다:

주체(Principal)

주체는 인증을 요청하는 사용자(일반적으로 브라우저를 통해)이다.

신원 공급자(Identity Provider, IdP)

신원 공급자는 사용자 인증을 수행하고 SAML 어설션(Assertion)을 발행하는 시스템이다.

주요 기능은 다음과 같다:

대표적인 IdP 제품/서비스:

서비스 공급자(Service Provider, SP)

서비스 공급자는 사용자가 접근하려는 애플리케이션이나 서비스이다.

주요 기능은 다음과 같다:

SAML 어설션(Assertions)

SAML의 핵심은 ‘어설션’이다.

어설션은 IdP가 생성하는 XML 문서로, 다음과 같은 정보를 포함한다:

  1. 인증 어설션(Authentication Assertions): 사용자가 인증되었음을 확인한다.
  2. 속성 어설션(Attribute Assertions): 사용자에 대한 추가 정보(이름, 이메일, 부서 등)를 제공한다.
  3. 권한 부여 결정 어설션(Authorization Decision Assertions): 사용자가 특정 리소스에 접근할 수 있는지 여부를 나타낸다.

SAML 어설션의 예:

 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
<saml:Assertion
   xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
   ID="_a75adf55-01d7-4acf-8891-e3bd5f36b63c"
   IssueInstant="2023-06-15T10:45:30Z"
   Version="2.0">
   <saml:Issuer>https://idp.example.org</saml:Issuer>
   <saml:Subject>
      <saml:NameID>kim@example.org</saml:NameID>
   </saml:Subject>
   <saml:Conditions
      NotBefore="2023-06-15T10:45:30Z"
      NotOnOrAfter="2023-06-15T11:45:30Z">
   </saml:Conditions>
   <saml:AuthnStatement
      AuthnInstant="2023-06-15T10:30:45Z">
      <saml:AuthnContext>
         <saml:AuthnContextClassRef>
            urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
         </saml:AuthnContextClassRef>
      </saml:AuthnContext>
   </saml:AuthnStatement>
   <saml:AttributeStatement>
      <saml:Attribute Name="email">
         <saml:AttributeValue>kim@example.org</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="role">
         <saml:AttributeValue>manager</saml:AttributeValue>
      </saml:Attribute>
   </saml:AttributeStatement>
</saml:Assertion>

SAML 흐름(Flows)

SAML에는 두 가지 주요 흐름이 있다:

SP-초기화 흐름(SP-Initiated Flow)

이 흐름에서는 사용자가 먼저 서비스 공급자(SP)에 접근한다:

  1. 사용자가 SP 웹사이트/서비스에 접근을 시도한다.
  2. SP는 사용자가 인증되지 않았음을 감지하고 SAML 인증 요청을 생성한다.
  3. SP는 사용자 브라우저를 IdP로 리다이렉트한다(SAML 요청 포함).
  4. IdP는 사용자 인증을 수행한다(로그인 화면 표시 등).
  5. 인증 성공 후, IdP는 SAML 어설션을 생성한다.
  6. IdP는 사용자 브라우저를 SAML 어설션과 함께 SP로 다시 리다이렉트한다.
  7. SP는 SAML 어설션을 검증하고 사용자에게 접근 권한을 부여한다.

IdP-초기화 흐름(IdP-Initiated Flow)

이 흐름에서는 사용자가 먼저 신원 공급자(IdP)에 접근한다:

  1. 사용자가 IdP 포털에 로그인한다.
  2. 사용자는 접근하려는 애플리케이션(SP)을 선택한다.
  3. IdP는 SAML 어설션을 생성한다.
  4. IdP는 사용자 브라우저를 SAML 어설션과 함께 SP로 리다이렉트한다.
  5. SP는 SAML 어설션을 검증하고 사용자에게 접근 권한을 부여한다.

SAML 구현 예제: Python

다음은 Python에서 SAML SP(서비스 공급자)를 구현하는 간단한 예제:

  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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# SAML 서비스 공급자(SP) 구현 예제
# 필요한 라이브러리: python3-saml

from flask import Flask, request, redirect, session, url_for
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
import os

app = Flask(__name__)
app.secret_key = '안전한_비밀_키_사용'

# SAML 설정을 로드하는 함수
def get_saml_settings():
    # 실제 구현에서는 설정 파일에서 로드하는 것이 좋습니다
    return {
        "strict": True,
        "debug": True,
        "sp": {
            "entityId": "https://sp.example.com/metadata/",
            "assertionConsumerService": {
                "url": "https://sp.example.com/acs/",
                "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            },
            "singleLogoutService": {
                "url": "https://sp.example.com/sls/",
                "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            },
            "x509cert": "",
            "privateKey": ""
        },
        "idp": {
            "entityId": "https://idp.example.org/metadata/",
            "singleSignOnService": {
                "url": "https://idp.example.org/sso/",
                "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            },
            "singleLogoutService": {
                "url": "https://idp.example.org/slo/",
                "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            },
            "x509cert": "IdP의_인증서_문자열"
        },
        "security": {
            "nameIdEncrypted": False,
            "authnRequestsSigned": False,
            "logoutRequestSigned": False,
            "logoutResponseSigned": False,
            "signMetadata": False,
            "wantMessagesSigned": False,
            "wantAssertionsSigned": False,
            "wantNameId": True,
            "wantNameIdEncrypted": False,
            "wantAssertionsEncrypted": False,
            "allowSingleLabelDomains": False,
            "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
            "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
        }
    }

# OneLogin_Saml2_Auth 객체를 초기화하는 함수
def init_saml_auth(req):
    settings = OneLogin_Saml2_Settings(get_saml_settings())
    return OneLogin_Saml2_Auth(req, settings)

# 요청 데이터를 OneLogin_Saml2_Auth에 필요한 형식으로 변환
def prepare_flask_request(request):
    url_data = request.url
    return {
        'https': 'on' if request.scheme == 'https' else 'off',
        'http_host': request.host,
        'server_port': request.environ.get('SERVER_PORT', ''),
        'script_name': request.path,
        'get_data': request.args.copy(),
        'post_data': request.form.copy(),
        'query_string': request.query_string.decode('utf-8')
    }

@app.route('/')
def index():
    if 'saml_attributes' in session:
        # 인증된 사용자라면 환영 메시지 표시
        attributes = session['saml_attributes']
        return f"로그인 성공! 사용자: {attributes.get('name', [''])[0]}, 이메일: {attributes.get('email', [''])[0]}"
    else:
        # 인증되지 않은 사용자라면 로그인 링크 표시
        return '<a href="/login/">SAML 로그인</a>'

@app.route('/login/')
def login():
    # SP-초기화 SSO 프로세스 시작
    req = prepare_flask_request(request)
    auth = init_saml_auth(req)
    
    # IdP로 리다이렉트
    return redirect(auth.login())

@app.route('/acs/', methods=['POST'])
def acs():
    # IdP로부터의 SAML 응답 처리
    req = prepare_flask_request(request)
    auth = init_saml_auth(req)
    
    # SAML 응답 처리
    auth.process_response()
    errors = auth.get_errors()
    
    if len(errors) == 0 and auth.is_authenticated():
        # 인증 성공
        session['saml_attributes'] = auth.get_attributes()
        session['saml_name_id'] = auth.get_nameid()
        return redirect(url_for('index'))
    else:
        # 인증 실패
        return f"오류: {', '.join(errors)}"

@app.route('/logout/')
def logout():
    # 로그아웃 프로세스 시작
    req = prepare_flask_request(request)
    auth = init_saml_auth(req)
    
    # 세션 정보 삭제
    session.clear()
    
    # IdP 로그아웃 페이지로 리다이렉트
    return redirect(auth.logout())

@app.route('/metadata/')
def metadata():
    # SP 메타데이터 생성 및 제공
    req = prepare_flask_request(request)
    auth = init_saml_auth(req)
    
    settings = auth.get_settings()
    metadata = settings.get_sp_metadata()
    errors = settings.validate_metadata(metadata)
    
    if len(errors) == 0:
        return metadata, 200, {'Content-Type': 'text/xml'}
    else:
        return f"오류: {', '.join(errors)}"

if __name__ == '__main__':
    app.run(debug=True)

이 코드는 Flask 웹 애플리케이션에서 SAML 서비스 공급자를 구현한 예이다.

주요 구성 요소는 다음과 같다:

SAML의 장단점

장점

  1. 중앙화된 인증: 사용자 자격 증명을 한 곳에서 관리할 수 있어 보안이 향상된다.
  2. 통합 사용자 경험: 사용자는 여러 애플리케이션에 개별적으로 로그인할 필요가 없어 경험이 향상된다.
  3. 강력한 보안: XML 서명과 암호화를 통해 메시지 무결성과 기밀성을 보장한다.
  4. 엔터프라이즈 지원: 대부분의 기업용 애플리케이션과 서비스에서 SAML을 지원한다.
  5. 상호 운용성: 다양한 플랫폼과 공급업체 간에 호환된다.
  6. 감사 및 규정 준수: 중앙화된 인증을 통해 감사 추적 및 규정 준수가 용이해진다.

단점

  1. 복잡성: 구현과 디버깅이 복잡하며 XML 처리가 필요하다.
  2. 모바일 한계: 모바일 애플리케이션에서는 제한적으로 사용된다.
  3. 오버헤드: XML 형식은 JSON에 비해 더 많은 대역폭을 사용한다.
  4. 설정 복잡성: SP와 IdP 간의 초기 설정이 복잡하다.
  5. 최신 앱 통합의 어려움: 최신 SPA(Single Page Application) 및 API 중심 아키텍처와의 통합이 어려울 수 있다.

SAML의 실제 활용 사례

  1. 기업 포털: 직원들이 한 번의 로그인으로 여러 내부 시스템에 접근할 수 있다.
  2. 클라우드 서비스 액세스: Salesforce, Office 365, Google Workspace 등의 클라우드 서비스에 기업 자격 증명으로 접근한다.
  3. 대학 시스템: 학생과 교직원이 학교 포털, 도서관 시스템, 학습 관리 시스템 등에 단일 로그인으로 접근한다.
  4. 정부 시스템: 공무원들이 여러 정부 시스템에 안전하게 접근한다.
  5. 의료 정보 시스템: 의료진이 다양한 의료 애플리케이션에 단일 인증으로 접근한다.

SAML 구현의 모범 사례

  1. 암호화 사용: 민감한 사용자 정보가 포함된 어설션은 암호화해야 한다.
  2. 서명 검증: 모든 SAML 메시지와 어설션의 서명을 반드시 검증해야 한다.
  3. 적절한 타임아웃 설정: 어설션의 유효 기간을 적절히 설정하여 재생 공격을 방지한다.
  4. 메타데이터 교환 자동화: IdP와 SP 간의 메타데이터 교환을 자동화하여 오류를 줄인다.
  5. SSL/TLS 사용: 모든 SAML 통신은 SSL/TLS를 통해 이루어져야 한다.
  6. 속성 매핑 표준화: IdP와 SP 간의 사용자 속성 매핑을 명확히 정의한다.
  7. 로깅 및 모니터링: 인증 이벤트를 철저히 로깅하고 모니터링한다.

용어 정리

용어설명

참고 및 출처