디스커버리
디스커버리 (OpenID Connect Discovery)
서비스가 많아지면 "이 OpenID Provider(OP)의 인가 엔드포인트는 어디지? 토큰 엔드포인트는? 공개 키는 어디서 가져오지?"를 하나하나 하드코딩하기가 싫어져요. OpenID Connect Discovery는 이런 OP의 설정을 한 번에 알아내는 표준 방법을 제공해요. OP가 광고하는 메타데이터를 읽으면 필요한 엔드포인트와 지원 기능을 알 수 있지요. 이 글에서는 Discovery가 메타데이터를 어떻게 노출하고, 어떤 필드로 구성되는지 살펴볼게요.
본문
OpenID Provider 메타데이터
Discovery를 지원하는 OP는 자신의 설정(metadata)을 문서로 제공해요. RP(신뢰 당사자)는 이 메타데이터를 읽어 필요한 엔드포인트와 지원 사항을 파악하지요. 주요 구성 필드는 이래요.
issuer(REQUIRED) — OP가 자신의 발급자 식별자(Issuer Identifier)로 주장하는httpsURL. 쿼리나 프래그먼트 성분은 없어요. WebFinger로 발급자를 알아낸다면, 그 값과 ID 토큰의iss클레임 값 모두 이 값과 같아야 해요.authorization_endpoint(REQUIRED) — OP의 OAuth 2.0 인가 엔드포인트 URL.https스킴이어야 하고 포트·경로·쿼리 파라미터 성분은 가질 수 있어요.token_endpoint— OP의 OAuth 2.0 토큰 엔드포인트 URL. Implicit Flow만 쓰지 않는다면 REQUIRED예요.userinfo_endpoint(RECOMMENDED) — OP의 UserInfo 엔드포인트 URL.jwks_uri(REQUIRED) — OP의 JWK Set 문서 URL. RP가 OP의 서명을 검증하는 데 쓰는 서명 키(그리고 선택적으로 암호화 키)를 담고, 반드시https스킴이어야 해요. 개인 키나 대칭 키는 절대 담으면 안 돼요.registration_endpoint(RECOMMENDED) — OP의 동적 클라이언트 등록(Dynamic Client Registration) 엔드포인트 URL.scopes_supported(RECOMMENDED) — 서버가 지원하는 OAuth 2.0 스코프 값 배열.openid스코프는 반드시 지원해야 해요.response_types_supported(REQUIRED) — OP가 지원하는response_type값 배열. 동적 OP는code,id_token,id_token token값을 지원해야 해요.grant_types_supported(OPTIONAL) — 지원하는 그랜트 타입 배열. 동적 OP는authorization_code와implicit을 지원해야 해요.subject_types_supported(REQUIRED) — 지원하는 주체 식별자 타입 배열. 유효한 타입은pairwise와public이에요.id_token_signing_alg_values_supported(REQUIRED) — ID 토큰 서명에 지원하는 JWS 알고리즘(alg값) 배열.RS256은 반드시 포함해야 해요.token_endpoint_auth_methods_supported(OPTIONAL) — 토큰 엔드포인트가 지원하는 클라이언트 인증 방식.client_secret_post,client_secret_basic,client_secret_jwt,private_key_jwt가 옵션이고, 생략하면 기본값은client_secret_basic(HTTP Basic)이에요.
이 밖에도 response_modes_supported, acr_values_supported, request_object_signing_alg_values_supported, claims_supported 등 많은 선택 필드가 더 정의돼 있어요.
설정 정보 얻기: /.well-known/openid-configuration
Discovery를 지원하는 OP는 /.well-known/openid-configuration 문자열을 발급자(issuer)에 이어 붙인 경로에 JSON 문서를 두어야 해요. 문법은 RFC 5785의 well-known 규칙을 따르고, 응답은 application/json 콘텐츠 타입이어야 해요.
예를 들어 발급자가 https://example.com이면 다음과 같이 요청해요.
GET /.well-known/openid-configuration HTTP/1.1
Host: example.com
발급자가 경로 성분을 가질 땐(예: https://example.com/issuer1) 끝의 /를 제거한 뒤 이어 붙여요.
GET /issuer1/.well-known/openid-configuration HTTP/1.1
Host: example.com
경로 성분을 쓰면 호스트 하나로 여러 발급자를 지원할 수 있어요. 멀티테넌트 호스팅 구성에서 필요한 방식이에요.
응답은 성공 시 200 OK 상태 코드와 application/json 콘텐츠 타입으로, 3장에서 정의한 메타데이터 값들의 부분집합을 멤버로 가진 JSON 객체를 돌려줘요. 여러 값을 가지는 클레임은 JSON 배열로 표현해요. 실제 예시는 다음과 같아요.
HTTP/1.1 200 OK
Content-Type: application/json
{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/connect/authorize",
"token_endpoint": "https://server.example.com/connect/token",
"token_endpoint_auth_methods_supported": ["client_secret_basic", "private_key_jwt"],
"userinfo_endpoint": "https://server.example.com/connect/userinfo",
"check_session_iframe": "https://server.example.com/connect/check_session",
"end_session_endpoint": "https://server.example.com/connect/end_session",
"jwks_uri": "https://server.example.com/jwks.json",
"registration_endpoint": "https://server.example.com/connect/register"
}
브라우저 기반(JavaScript) 클라이언트가 이 설정을 직접 읽을 수 있도록, openid-configuration 엔드포인트와 여러 엔드포인트는 CORS를 지원하는 것이 좋아요. 다만 인가 엔드포인트는 클라이언트가 직접 접근하지 않고 리다이렉트되는 곳이라 CORS 사용을 권장하지 않아요.
더 알아보기
- 발급자를 WebFinger로 알아내는 방법은 Discovery 스펙의 2절 Issuer Discovery를 참고해요.
end_session_endpoint가 실제로 쓰이는 로그아웃 흐름은 RP-Initiated Logout 문서를 참고해요.- JWKS가 무엇인지 더 알고 싶다면 JSON Web Key(JWK) 스펙을 참고해요.