개발자
SSO·SCIM 연동 명세
OIDC·SAML 2.0·SCIM 2.0 연동에 필요한 값과 동작 명세입니다.
화면 설정 순서는 SSO와 SCIM 참고.
표의 {API}는 사용하는 환경의 Keural API 주소입니다(예: https://api.keural.ai). 실제 값은 조직 관리 → SSO 설정 화면에 표시된 주소를 씁니다.
등록 값 요약
| 항목 | 값 |
|---|---|
| OIDC 리디렉션 URI | {API}/api/sso/callback |
| OIDC·SAML 로그인 시작 | {API}/api/sso/{orgId}/login |
| SAML SP EntityID | {API}/api/sso/saml/metadata |
| SAML ACS URL | {API}/api/sso/saml/callback |
| SCIM Base URL | {API}/scim/v2 |
로그인은 항상 Keural에서 시작합니다(SP-initiated). 로그인 화면에 등록된 도메인의 이메일을 입력하면 로그인 시작 주소로 이동합니다. 성공하면 Keural 첫 화면으로, 실패하면 /login?error=sso로 이동합니다.
OIDC
| 항목 | 동작 |
|---|---|
| 흐름 | Authorization Code |
| authorize 요청 | response_type=code, client_id, redirect_uri, scope=openid email profile, state, nonce |
| state · nonce | 1회용이며 10분 동안 유효합니다. 로그인 창을 10분 넘게 열어 두면 처음부터 다시 로그인합니다. |
| 토큰 교환 | POST token endpoint, application/x-www-form-urlencoded, 클라이언트 인증은 client_secret_post(본문에 client_id·client_secret) |
| ID 토큰 서명 | RS256만 받습니다. 키는 jwks_uri에서 kid로 찾습니다. |
| ID 토큰 검증 | 서명, exp, aud에 client_id 포함, nonce 일치, iss가 Issuer URL과 일치(끝의 / 무시) |
| 이메일 | ID 토큰의 email 클레임. 없으면 userinfo endpoint를 Authorization: Bearer <access_token>으로 불러 email을 읽습니다. |
| 이름 | ID 토큰의 name 클레임 |
| 엔드포인트 | Issuer URL의 /.well-known/openid-configuration에서 찾습니다. 찾을 수 없는 IdP는 고급 설정 열기에서 authorization · token · JWKS · userinfo 주소를 직접 입력합니다. |
| 시간 제한 | IdP 호출마다 15초 |
IdP 애플리케이션은 client_secret_post를 허용해야 합니다. client_secret_basic이나 private_key_jwt만 허용하는 설정이면 토큰 교환이 실패합니다.
SAML 2.0
| 항목 | 동작 |
|---|---|
| 요청 | AuthnRequest를 HTTP-Redirect 바인딩으로 보냅니다. 요청에는 서명하지 않습니다. |
| 요청 내용 | Destination=IdP SSO URL, AssertionConsumerServiceURL=ACS URL, ProtocolBinding=HTTP-POST, Issuer=SP EntityID, NameIDPolicy Format=…:nameid-format:unspecified, AllowCreate=true |
| 응답 | HTTP-POST 바인딩, SAMLResponse와 RelayState. RelayState는 Keural이 보낸 값을 그대로 반환해야 합니다. |
| 서명 | Response 또는 Assertion 중 하나에 유효한 서명이 있어야 합니다. 검증 키는 화면에 등록한 인증서만 씁니다. |
| 어설션 | 응답 안에 어설션이 정확히 하나 있어야 합니다. 암호화된 어설션(EncryptedAssertion)은 받지 않습니다. |
| 상태 | StatusCode가 urn:oasis:names:tc:SAML:2.0:status:Success |
| Issuer | IdP EntityID를 넣었으면 어설션 Issuer와 일치 |
| 조건 | NotBefore·NotOnOrAfter(허용 오차 300초), Audience=SP EntityID |
| SubjectConfirmationData | Recipient=ACS URL, NotOnOrAfter(허용 오차 300초), InResponseTo=Keural이 보낸 요청 ID |
| 이메일 | 속성 Name 또는 FriendlyName이 다음 중 하나(대소문자 무시): email, mail, emailAddress, E-Mail-Addresses, http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress. 없으면 @가 들어간 NameID를 씁니다. |
| 이름 | displayName, name, cn, http://schemas.microsoft.com/identity/claims/displayname |
| 인증서 | X.509 한 개. PEM 또는 Base64(DER). 교체할 때는 새 인증서를 입력하고 저장합니다. |
| 메타데이터 | IdP 메타데이터 XML을 올리는 대신 IdP EntityID · IdP SSO URL · 인증서 세 값을 입력합니다. |
사용자 판정
| 항목 | 동작 |
|---|---|
| SSO로 보내는 기준 | 이메일 @ 뒤가 이메일 도메인 목록의 한 항목과 정확히 같을 때(대소문자 무시). 하위 도메인은 따로 등록합니다. |
| 계정 찾기 | IdP가 준 이메일(소문자)로 Keural 계정을 찾습니다. |
| 계정이 없을 때 | 미가입 사용자 자동 생성(JIT)이 켜져 있으면 만들고, 꺼져 있으면 로그인을 거부합니다. |
| 조직 합류 | 조직 멤버가 아니면 멤버로 합류합니다. 좌석이 찼거나 다른 조직에 속한 계정은 합류하지 않고 로그인만 됩니다. |
| 이메일 도메인 저장 조건 | DNS TXT로 인증한 도메인, 공개 이메일 도메인 제외 |
도메인 인증
| 항목 | 값 |
|---|---|
| 레코드 종류 | TXT |
| 호스트 | 등록한 도메인 그대로(예: acme.com). 접두 하위 도메인은 쓰지 않습니다. |
| 값 | keural-verify=<UUID> — 화면에 나온 값 그대로 |
| 확인 시점 | 인증 확인을 누를 때 한 번 조회합니다. |
SCIM 2.0
| 항목 | 값 |
|---|---|
| Base URL | {API}/scim/v2 |
| 인증 | Authorization: Bearer <SCIM 토큰>. 토큰은 scim_으로 시작하며 발급 때 한 번만 보입니다. |
| Content-Type | 요청 application/scim+json 또는 application/json, 응답 application/scim+json |
| 상태 코드 | 실제 HTTP 상태 코드로 반환합니다. 오류 본문은 SCIM Error 형식(schemas, status, detail, scimType)입니다. |
엔드포인트
| 메서드 | 경로 | 동작 |
|---|---|---|
GET | /Users | 목록. filter=userName eq "…", startIndex(기본 1), count(기본 100, 최대 200) |
GET | /Users/{id} | 한 명 조회 |
POST | /Users | 생성(201). 같은 이메일이 있으면 409 |
PATCH | /Users/{id} | active, displayName, name.formatted, name.givenName 변경 |
PUT | /Users/{id} | displayName·name·active 반영. active를 빼면 활성으로 봅니다. |
DELETE | /Users/{id} | 계정 정지(204). 데이터는 지우지 않습니다. |
GET | /Groups, /Groups/{id} | 고정 그룹 두 개 조회. filter=displayName eq "…" |
PATCH | /Groups/{id} | 멤버 추가·제거·교체 |
GET | /ServiceProviderConfig, /ResourceTypes, /Schemas | 지원 기능 조회 |
사용자 속성
| 속성 | 쓰임 |
|---|---|
userName | 이메일. 없으면 emails[0].value를 씁니다. |
displayName · name.formatted · name.givenName+name.familyName | 표시 이름 |
active | false면 정지(로그인 차단), true면 복구 |
id | Keural 사용자 ID. 응답에 담깁니다. |
externalId는 저장하지 않습니다. IdP에서 사용자를 찾을 때는 userName 필터를 씁니다.
그룹
| id | displayName | 멤버를 넣으면 | 멤버를 빼면 |
|---|---|---|---|
keural-admins | Keural Admins | 관리자 | 멤버 |
keural-members | Keural Members | 멤버 | 변화 없음 |
조직 소유자의 역할은 SCIM으로 바뀌지 않습니다. 그룹 생성·삭제(POST·DELETE /Groups)는 501을 반환합니다. 그룹 멤버의 value는 Keural 사용자 ID(/Users의 id)입니다.
지원 범위
| 기능 | 지원 |
|---|---|
| PATCH | 예 |
| 필터 | userName eq, displayName eq |
| Bulk · 정렬 · ETag · 비밀번호 변경 | 아니요 |
오류
| 상태 | scimType | 원인 |
|---|---|---|
400 | invalidValue | userName과 emails가 모두 없음 |
401 | — | 토큰이 없거나 틀림, SCIM 프로비저닝이 꺼져 있음 |
404 | — | 이 조직의 사용자나 그룹이 아님 |
409 | uniqueness | 같은 이메일의 계정이 이미 있음, 좌석 초과 |
501 | — | 그룹 생성·삭제 |