OpenID Connect (OIDC)

OpenID Connect(OIDC)는 웹 기반 애플리케이션과 서비스를 위한 현대적인 인증 프로토콜로, OAuth 2.0 프레임워크를 기반으로 구축되었다. 이 프로토콜은 사용자의 신원을 검증하고, 안전하게 정보를 교환하는 표준화된 방법을 제공한다.

OIDC의 역사와 배경

OpenID Connect는 기존 OpenID 2.0의 한계를 극복하기 위해 2014년에 공식적으로 출시되었다. OpenID Foundation이 개발한 이 프로토콜은 인증(Authentication)에 중점을 두고, OAuth 2.0의 권한 부여(Authorization) 기능을 보완한다.

초기 웹에서는 각 서비스마다 독립적인 사용자 계정과 인증 시스템이 필요했다. 이러한 분산된 접근 방식은 사용자에게 불편함을 주고, 보안 위험을 증가시켰다. OpenID Connect는 이러한 문제를 해결하기 위해 등장했으며, 현재 Google, Microsoft, Facebook 등 주요 기술 기업들이 이 표준을 채택하고 있다.

OIDC와 OAuth 2.0의 관계

많은 개발자들이 혼동하는 부분이지만, OIDC와 OAuth 2.0은 서로 다른 목적을 가지고 있다:

  • OAuth 2.0: 서비스가 사용자의 데이터에 접근할 수 있는 ‘권한을 부여’하는 프레임워크이다.
  • OpenID Connect: OAuth 2.0 위에 구축된 ‘인증 레이어’로, 사용자의 신원을 확인한다.

쉽게 비유하자면, 호텔에 비유할 수 있다. OAuth 2.0은 객실 카드키(접근 권한)를 제공하는 것이고, OIDC는 체크인 과정에서 신분증(신원 확인)을 확인하는 것이다.

OIDC의 핵심 구성 요소

OpenID Connect는 다음과 같은 핵심 요소로 구성된다:

  1. ID 토큰(ID Token): JWT(JSON Web Token) 형식으로 사용자의 신원 정보를 포함한다. 이 토큰은 서명되어 있어 무결성을 보장한다.
  2. UserInfo 엔드포인트: 추가적인 사용자 정보를 요청할 수 있는 REST API이다.
  3. 표준 클레임(Claims): 이름, 이메일, 프로필 사진 등 사용자 정보에 대한 표준화된 필드이다.
  4. 디스커버리(Discovery): ID 제공자의 기능과 엔드포인트를 자동으로 발견할 수 있는 메커니즘이다.

OIDC의 인증 흐름(Flow)

OIDC는 다양한 애플리케이션 유형에 맞는 여러 인증 흐름을 제공한다:

권한 부여 코드 흐름(Authorization Code Flow)

서버 측 애플리케이션에 가장 적합한 이 흐름은 다음과 같이 작동한다:

1
2
3
4
5
6
7
8
9
+--------+                                    +--------+
|        |---(1) 인증 요청----------------->|        |
|        |                                    |        |
|        |<--(2) 권한 부여 코드--------------|        |
|   RP   |                                    |   IdP  |
|        |---(3) 토큰 요청------------------>|        |
|        |                                    |        |
|        |<--(4) ID 토큰 & 액세스 토큰-------|        |
+--------+                                    +--------+

암시적 흐름(Implicit Flow)

브라우저 기반 애플리케이션(특히 SPA)을 위한 단순화된 흐름이다:

1
2
3
4
5
6
7
+--------+                                    +--------+
|        |---(1) 인증 요청----------------->|        |
|        |                                    |        |
|   RP   |<--(2) ID 토큰 & 액세스 토큰-------|   IdP  |
|        |    (리다이렉트 URI로 전달)        |        |
|        |                                    |        |
+--------+                                    +--------+

하이브리드 흐름(Hybrid Flow)

권한 부여 코드 흐름과 암시적 흐름의 요소를 결합한 방식이다.

ID 토큰 구조 이해하기

ID 토큰은 JWT(JSON Web Token) 형식으로, 세 부분으로 구성된다:

  1. 헤더(Header): 토큰 유형과 사용된 서명 알고리즘을 지정한다.
  2. 페이로드(Payload): 사용자 정보(클레임)를 포함한다.
  3. 서명(Signature): 토큰의 무결성을 보장한다.

ID 토큰의 일반적인 클레임에는 다음이 포함된다:

  • iss: 토큰 발급자(Issuer)
  • sub: 주체(Subject), 사용자의 고유 식별자
  • aud: 대상 청중(Audience), 토큰이 의도된 수신자
  • exp: 만료 시간(Expiration Time)
  • iat: 발급 시간(Issued At)
  • auth_time: 사용자 인증 시간
  • nonce: 재생 공격을 방지하기 위한 임의의 값

OIDC 통합 구현 예제

다음은 Python에서 Flask 웹 프레임워크를 사용한 OIDC 클라이언트 구현 예제:

 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
from flask import Flask, redirect, url_for, session, request, jsonify
from authlib.integrations.flask_client import OAuth
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)  # 세션 보안을 위한 비밀 키

# OAuth 클라이언트 설정
oauth = OAuth(app)
oauth.register(
    name='google',  # ID 제공자 이름
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',  # 디스커버리 URL
    client_id='YOUR_CLIENT_ID',  # 클라이언트 ID
    client_secret='YOUR_CLIENT_SECRET',  # 클라이언트 비밀키
    client_kwargs={
        'scope': 'openid email profile'  # 요청할 스코프
    }
)

@app.route('/')
def home():
    # 세션에서 사용자 정보 확인
    user = session.get('user')
    if user:
        return f'로그인 성공! 사용자: {user["name"]}, 이메일: {user["email"]}'
    return '<a href="/login">Google로 로그인</a>'

@app.route('/login')
def login():
    # Google 로그인 페이지로 리다이렉트
    redirect_uri = url_for('auth', _external=True)
    return oauth.google.authorize_redirect(redirect_uri)

@app.route('/auth')
def auth():
    # 콜백 처리
    token = oauth.google.authorize_access_token()  # 토큰 획득
    
    # ID 토큰에서 사용자 정보 추출
    user_info = oauth.google.parse_id_token(token)
    
    # 세션에 사용자 정보 저장
    session['user'] = user_info
    
    return redirect('/')

@app.route('/logout')
def logout():
    # 세션에서 사용자 정보 삭제
    session.pop('user', None)
    return redirect('/')

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

이 코드는 다음과 같은 과정을 구현한다:

  1. 사용자가 ‘/login’ 경로에 접근하면 Google의 인증 페이지로 리다이렉트된다.
  2. 사용자가 Google에서 인증하면 ‘/auth’ 경로로 리다이렉트되고, 애플리케이션은 ID 토큰과 액세스 토큰을 받는다.
  3. ID 토큰에서 사용자 정보를 추출하여 세션에 저장한다.
  4. 사용자는 이제 인증된 상태로 애플리케이션을 사용할 수 있다.

OIDC 보안 고려사항

OIDC를 안전하게 구현하기 위해 고려해야 할 중요한 보안 사항들:

  1. 토큰 검증: ID 토큰의 서명, 발급자, 대상 청중, 만료 시간 등을 필수적으로 검증해야 한다.
  2. HTTPS 사용: 모든 OIDC 통신은 HTTPS를 통해 이루어져야 한다.
  3. 상태(State) 파라미터: CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해 상태 파라미터를 사용해야 한다.
  4. 넌스(Nonce) 값: 재생 공격을 방지하기 위해 넌스 값을 사용하고 검증해야 한다.
  5. 최소 권한 원칙: 애플리케이션에 필요한 최소한의 스코프만 요청해야 한다.

OIDC의 장점

  1. 표준화: 잘 정의된 프로토콜로 다양한 구현체 간의 상호 운용성을 보장한다.
  2. 사용자 경험 향상: 사용자는 여러 서비스에 동일한 계정으로 로그인할 수 있어 편리하다.
  3. 보안 강화: 전문적인 ID 제공자가 인증을 담당하므로 보안 위험이 감소한다.
  4. 개발 효율성: 복잡한 인증 로직을 직접 구현할 필요가 없어 개발 시간과 비용이 절감된다.
  5. 중앙화된 로그아웃: 단일 로그아웃(Single Logout)을 통해 모든 연결된 서비스에서 동시에 로그아웃할 수 있다.

OIDC의 도전과제

  1. 프라이버시 우려: 중앙화된 ID 제공자는 사용자의 로그인 패턴과 서비스 사용에 대한 정보를 수집할 수 있다.
  2. 의존성 문제: ID 제공자 서비스가 중단되면 모든 연결된 서비스에 접근할 수 없게 된다.
  3. 구현 복잡성: 전체 OIDC 스펙은 복잡하며, 올바른 구현에는 세심한 주의가 필요하다.
  4. 신뢰성 문제: ID 제공자가 사용자 데이터를 오용할 가능성이 있다.

OIDC 확장과 프로파일

OIDC는 다양한 사용 사례를 지원하기 위한 여러 확장과 프로파일을 제공한다:

  1. FAPI(Financial-grade API): 금융 서비스를 위한 보안 강화 프로파일이다.
  2. CIBA(Client Initiated Backchannel Authentication): 디바이스 간 인증을 위한 확장이다.
  3. SIOP(Self-Issued OpenID Provider): 사용자가 자신의 ID 제공자가 될 수 있는 방식이다.
  4. HEART(Health Relationship Trust): 의료 데이터 접근을 위한 프로파일이다.

용어 정리

용어설명

참고 및 출처