OAuth에 이어, OIDC에 대해 알아보자
개발자 입장에서 로그인이나 회원 가입 등 사용자 정보를 직접 관리하는 것은 어찌 보면 당연한 일이지만, 동시에 굉장히 부담스러운 일이기도 하다. 최근 발생하는 다양한 보안 이슈만 보더라도, 이 작업이 얼마나 어려운 일인지 알 수 있다.
시간이 지날수록 우리가 사용하는 서비스의 종류는 점점 늘어나고 있다. 이는 사용자에게도 부담스러운 일이다. 내 정보를 얼마나 안전하게 보관하느냐는 차치하더라도, 각각의 서비스에 회원 가입을 하고 비밀번호를 기억하고 관리하는 일은 이제 걱정을 넘어 귀찮음의 영역에 가까워지고 있다.
만약 우리 서비스를 이용하는 사용자의 정보를 안전하게 관리할 수 있는 플랫폼에 위임하고, 사용자가 하나의 인증 정보로 여러 서비스를 이용할 수 있다면 어떨까? 그 해답이 바로 OIDC(OpenID Connect)에 있다. 이번 포스팅에서는 OIDC에 대해 알아보자.
OpenID(OIDC)란?
OpenID는 비영리 단체인 OpenID Foundation에서 추진하는 개방형 표준, 분산 인증 프로토콜이다.
OpenID를 사용하면 사용자는 구글, 페이스북과 같은 제3자 ID공급자(IDP)서비스를 사용하여 인증을 받을 수 있다. 즉, 우리 서비스를 만들기 위해 사용자가 새로 회원가입을 하지 않고 이미 사용 중인 서비스를 통해 인증을 진행할 수 있다.
OpenID Connect는 2014년에 발표된 비교적 새로운 프로토콜로, 기존의 OpenID 1.0/2.0과는 완전히 다른 접근 방식을 취한다. OIDC는 OAuth 2.0을 기반으로 구축되어 더 안전하고 현대적인 인증 방식을 제공한다. 이번 포스팅에서는 바로 이 OIDC를 기준으로 설명한다.
OpenID 주요 구성요소
OIDC의 주요 구성요소는 다음과 같다.
- EU (End User)
- 인증을 받고 서비스를 이용하려는 실제 사용자
- RP (Relying Party)
- 사용자를 대신해 인증을 요청하고, 인증 결과를 받아 활용하는 애플리케이션 또는 서비스
- OP (OpenID Provider)
- OIDC 사양을 충족하는 IDP(사용자의 신원을 인증하고, 인증결과를 RP에 전달하는 인증서버)
OAuth2.0과 OIDC
공식 문서에서는 OIDC를 다음과 같이 설명한다.
“OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.”
OpenID Connect 1.0은 OAuth 2.0 프로토콜 위에 구축된 간단한 신원 계층입니다.
OAuth 2.0 프로토콜 위에 구축된 간단한 신원 계층이라는 설명이 잘 와닿지 않는다. OIDC는 OAuth 2.0 위에 인증 레이어를 추가한 프로토콜이다. 즉, OAuth 2.0의 모든 기능을 포함하면서 추가로 사용자 인증 기능을 제공한다는 의미이다.
인가 VS 인증
OAuth 2.0은 인가(Authorization)에 중점을 둔 프로토콜로, 사용자가 리소스에 접근할 권한을 부여받는 과정에 집중한다. 반면, OIDC는 OAuth 2.0을 확장하여 인증(Authentication) 기능을 추가한 프로토콜이다. 즉, OIDC는 사용자의 신원을 검증하고, ID Token을 통해 사용자 프로필 정보를 제공에 집중한다.
OAuth 2.0은 AccessToken을 받는다. 이 토큰은 보호된 리소스에 접근할 때 사용할 수 있는 “입장권”의 역할을 한다. AccessToken을 통해 리소스를 가지고 있는 서버에서 사용자의 접근 권한만 확인할 수 있다.
OIDC는 AccessToken을과 더불어 ID Token도 받는다. 이 중 ID Token은 JWT(JSON Web Token)으로 제공되며 이 토큰은 사용자의 정보를 담고있다. 즉 “이름표”의 역할을 한다.
이처럼 OIDC는 OAuth 2.0의 인가 기능에 사용자 신원 확인 시스템을 추가함으로써, SSO(Single Sign-On) 등 보다 복잡한 인증 시나리오를 지원할 수 있습니다.
Scope
OAuth 2.0에서는 각 서비스 제공자가 원하는 대로 scope와 사용자 정보 형식을 정의할 수 있다. OIDC에서는 이를 어떻게 표준화했는지 알아보자.
다음 표는 OIDC 표준 scope이지만, 서비스 제공자가 지원하지 않을 수도 있다. 그렇기 때문에 사용 전 각 OP(OpenID Provider)에서 어떤 scope를 지원하는지 확인해야한다.
💡 참고: Google구글의 경우 OIDC scope 파라미터 문서를 참고하자.
| scope | 설명 | 필수 |
|---|---|---|
openid | OIDC 인증 요청임을 명시, sub(사용자 식별자) | O |
profile | 이름, 프로필 사진 등 기본 프로필 정보 | X |
email | 이메일 정보 | X |
address | 주소 정보 | X |
phone | 전화번호 | X |
offline_access | 리프레시 토큰 요청 | X |
1
2
3
4
5
# URL 인코딩
scope=openid%20profile
# 인코딩 전
scope=openid profile
이러한 표준화를 통해 개발자는 구글, 네이버, 카카오 등 서로 다른 OP(OpenID Provider)를 사용하더라도 동일한 방식으로 사용자 정보에 접근할 수 있다.
동작 과정
OIDC는 여러 방식으로 제공할 수 있다. 클라이언트에게 인가 코드(Authorization Code)를 반환하며, 클라이언트는 이를 ID Token과 AccessToken으로 직접 교환하는 Authorization Code Flow이 가장 많이 알려져 있다.
다음 work flow를 보면 Oauth2.0 과 동일하다. OAuth 2.0의 모든 기능을 포함하면서 추가로 사용자 인증 기능을 추가하는 것이 OIDC이기 때문에 어찌보면 당연한 이야기이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+--------+ +--------+
| | | |
| |---------(1) AuthN Request-------->| |
| | | |
| | +--------+ | |
| | | | | |
| | | End- |<--(2) AuthN & AuthZ-->| |
| | | User | | |
| RP | | | | OP |
| | +--------+ | |
| | | |
| |<--------(3) AuthN Response--------| |
| | | |
| |---------(4) UserInfo Request----->| |
| | | |
| |<--------(5) UserInfo Response-----| |
| | | |
+--------+ +--------+
- RP가 OP에 요청을 보낸다.
- OP가 EU를 인증하고 권한 부여를 받는다.
- OP가 ID Token와 AccessToken을 응답한다.
- RP는 AccessToken을을 사용하여 UserInfo엔드포인트에 요청을 보낼 수 있다. (Optional)
- UserInfo 엔드포인트는 최종 사용자에 대한 정보를 반환한다. (Optional)
ID Token 분석
아래 예시는 Google OAuth 2.0 Playground에서 Scope를 openid profile로 요청하고 받은 응답이다.
1
2
3
4
5
6
7
8
9
{
"access_token": "ya29.a0AW4Xtxj2IN7Xq8rfXgil...",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"expires_in": 3599,
"refresh_token_expires_in": 604799,
"token_type": "Bearer",
"scope": "openid https://www.googleapis.com/auth/userinfo.profile",
"refresh_token": "1//04PQselja6-4mCgYI..."
}
여기서 id_token은 JWT(JSON Web Token) 형태로 인코딩되어 있으며, 이를 디코딩하면 사용자의 신원 정보를 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"iss": "https://accounts.google.com",
"azp": "4074087xxxxx.apps.googleusercontent.com",
"aud": "4074087xxxxx.apps.googleusercontent.com",
"sub": "1002...",
"at_hash": "qpr....",
"name": "its",
"picture": "https://lh3.googleusercontent.com/...",
"given_name": "...",
"family_name": "...",
"iat": 1748262588,
"exp": 1748266188
}
| 클레임 | 설명 |
|---|---|
iss | 토큰 발급자(Issuer) |
azp | 인증된 당사자(Authorized Party) - 클라이언트 ID |
aud | 토큰 대상자(Audience) - 일반적으로 클라이언트 ID와 동일 |
sub | 사용자 고유 식별자(Subject) |
at_hash | Access Token의 해시값 (토큰 무결성 검증용) |
name | 사용자 전체 이름 |
picture | 프로필 이미지 URL |
given_name | 이름 |
family_name | 성 |
iat | 토큰 발급 시간 (Unix timestamp) |
exp | 토큰 만료 시간 (Unix timestamp) |
마무리
서문에서 언급했듯이, 개발자에게는 사용자 정보 관리가 부담스럽고, 사용자에게는 수많은 서비스의 회원가입과 비밀번호 관리가 번거로운 일이 되어가고 있다. OIDC는 바로 이러한 문제들을 효과적으로 해결할 수 있는 솔루션이다.
개발자 관점에서 OIDC는 사용자 정보 관리의 복잡성과 보안 책임을 신뢰할 수 있는 제3자 플랫폼에 위임할 수 있게 해준다. 패스워드 관리, 개인정보 보호, 보안 위협 대응 등의 부담을 크게 줄일 수 있으며, 하나의 통합된 인터페이스로 다양한 OP(OpenID Provider)들과 표준화된 방식으로 연동할 수 있다.
사용자 관점에서 OIDC는 하나의 인증 정보로 여러 서비스에 접근 할 수 있게 해준다. 이는 기억해야 할 패스워드의 수를 줄이고, 각 서비스마다 새로운 계정을 만들 필요 없이 이미 사용 중인 구글이나 페이스북 계정으로 간편하게 로그인할 수 있는 편의성을 제공한다.
OpenID Foundation에서 “OIDC는 현대 웹 애플리케이션에서 사용자 인증을 구현할 때 매우 유용한 프로토콜” 이라고 소개한다. 이번 포스팅을 통해서 OIDC는 OAuth 2.0의 견고한 기반 위에 인증 기능을 추가하여, 개발자는 복잡한 사용자 관리 시스템을 직접 구축하지 않고도 안전하고 표준화된 인증 시스템을 구현할 수 있게 해준다는 것을 알게되었으며, 위 소개에 깊은 공감을 할 수 있었다.
다음에 기회가 된다면, 실제로 Spring 환경에서 소셜 로그인 기능을 직접 구현해 보며 더 깊이 이해하는 시간을 가져봐야겠다.
