보안 감사가 끝난 뒤 보고서에서 이런 항목이 나왔다. 8개월 전에 퇴사한 시니어 엔지니어의 Salesforce 관리자 계정이 여전히 활성 상태였다. 그 계정으로 고객 데이터 전체를 export할 수 있는 권한이 붙어 있었다. 실제로 사용된 흔적은 없었지만, "사용된 흔적이 없다"는 것과 "사용되지 않았다"는 것은 다른 말이다.
어떻게 이런 일이 생겼는가. HR에서 퇴사 처리는 했다. IdP(Identity Provider, 사내 계정 원부를 관리하고 다른 서비스에 인증을 제공하는 시스템 — Okta, Azure AD, Google Workspace 같은 것들) 계정도 비활성화했다. 그런데 Salesforce는 자체 사용자 디렉토리를 별도로 관리하는 앱이었고, IdP 비활성화가 Salesforce에 전파되지 않았다. 담당자가 Salesforce를 체크리스트에서 빠뜨렸다. 직접 연동이 없으면 이런 누락은 구조적으로 반복된다.
이 전 포스트에서 다룬 IGA는 "퇴사자의 모든 권한이 즉시 회수됐는가"라는 질문을 제도화하는 시스템이다. 그런데 IGA가 답을 내렸어도, 그 결정을 실제로 각 앱에 전달하는 메커니즘이 없으면 아무 일도 일어나지 않는다. 계정을 생성하고, 수정하고, 비활성화하는 실행 레이어인 프로비저닝(provisioning)이 누락되었다는 증거이다.
Zero Trust는 모든 접근 요청을 검증한다. 그런데 검증의 근거인 identity 데이터가 앱마다 달라지거나 뒤처진다면, 정책 엔진의 판단은 틀린 전제 위에 놓인다. SCIM(System for Cross-domain Identity Management)은 그 데이터를 모든 앱에 정확하게 전달하는 인프라다. Zero Trust 정책이 신뢰할 수 있으려면, 그 아래에서 identity를 동기화하는 레이어가 먼저 작동해야 한다.
N×M 연동 문제
앱마다 커스텀 연동을 만드는 방식의 실제 비용을 먼저 이해해야 한다.
중간 규모 기업이 사용하는 SaaS 앱을 세어보면 보통 50~100개다. IdP가 하나고 앱이 50개면 연동 코드가 50개다. 그런데 실제로는 환경이 더 복잡하다. 프로덕션 IdP와 개발용 IdP가 분리돼 있으면 100개다. 기업 인수로 다른 IdP를 쓰는 조직이 합쳐지면 거기서 또 늘어난다.
IdP A ─── 연동 A-1 ───→ Slack
IdP A ─── 연동 A-2 ───→ GitHub
IdP A ─── 연동 A-3 ───→ Salesforce
...
IdP B ─── 연동 B-1 ───→ Slack
IdP B ─── 연동 B-2 ───→ GitHub
N개 IdP × M개 앱 = N×M개 연동. 각 연동은 해당 앱의 API 버전에 묶여 있다. 앱이 API를 올리면 연동이 깨진다. SaaS 앱들은 API를 자주 바꾼다. 유지보수 비용이 선형이 아니라 이차함수로 증가한다.
표준이 없을 때 반복되는 세 가지 패턴이 있다.
스펙 파편화: "사용자 비활성화"라는 동일한 작업이 앱마다 다르게 구현돼 있다. Slack은 admin.users.setInactive API, GitHub Enterprise Server는 PUT /users/{username}/suspended, Jira는 자체 User Management REST API를 제공한다. 각 앱의 문서를 따로 읽고 따로 구현해야 한다.
단방향 의존성: 연동 코드는 앱의 내부 구현 결정에 종속된다. 앱이 사용자 ID 체계를 바꾸거나 인증 방식을 업데이트하면 연동이 조용히 깨진다. 유지보수 담당자가 없거나 바뀌면 어떤 연동이 정상인지도 파악하기 어렵다.
감사 불가능성: 커스텀 연동마다 에러 처리와 로깅 방식이 다르다. "어제 퇴사한 직원의 Salesforce 계정이 실제로 비활성화됐는가?"를 확인하려면 연동 로그를 앱별로 찾아봐야 한다. 자동화된 감사 리포트를 만들 수 없다.
SCIM은 RFC 7642~7644로 정의된 IETF 표준이다. 핵심 아이디어는 단순하다. "모든 앱이 동일한 방식으로 사용자와 그룹 API를 노출하면, IdP는 하나의 코드로 모든 앱에 프로비저닝할 수 있다."

N×M에서 N+M으로. IdP는 SCIM 클라이언트 구현 하나로 모든 SCIM 서버와 통신한다. 앱은 SCIM 서버를 구현하면 SCIM을 지원하는 모든 IdP와 자동으로 연동된다.
ServiceProviderConfig — 계약서를 먼저 읽는다
SCIM 서버는 자신의 능력을 GET /ServiceProviderConfig 엔드포인트에 명시한다. 연동을 시작하기 전에 이것부터 읽는 것이 순서다. 이 응답이 IdP와 앱 사이의 계약서다.
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"patch": { "supported": true },
"bulk": { "supported": true, "maxOperations": 1000, "maxPayloadSize": 1048576 },
"filter": { "supported": true, "maxResults": 200 },
"changePassword": { "supported": false },
"sort": { "supported": false },
"etag": { "supported": true },
"authenticationSchemes": [
{ "type": "oauthbearertoken", "name": "OAuth Bearer Token" }
]
}
patch.supported가 false면 IdP가 PATCH를 사용할 수 없으므로 속성 수정 시 PUT을 사용해야 한다는 의미다.
etag.supported가 true면 서버가 ETag(아래에 설명) 기반 동시성 제어를 지원한다는 의미다. IdP가 If-Match 헤더를 함께 보내면 동시 수정 충돌을 감지할 수 있다. filter.maxResults가 200이면 단일 요청으로 최대 200개까지만 반환한다. 전체 목록을 가져오려면 페이지네이션이 필요하다.
연동 코드를 만들기 전에 ServiceProviderConfig를 읽지 않으면, PATCH를 지원하지 않는 앱에 PATCH를 시도하거나 Bulk가 없는 앱에 Bulk 요청을 보내는 런타임 오류가 발생한다. 스펙 확인은 런타임 오류를 설계 시점으로 당기는 작업이다.
스키마 정보도 함께 확인해야 한다. GET /Schemas를 통해 앱이 지원하는 속성과 그 타입을 알 수 있다. 앱이 department 속성을 지원하지 않는데 IdP가 이 값을 매핑하려 하면 해당 속성은 무시되거나 에러를 유발한다.
리소스 모델 — id와 externalId의 설계 의도
SCIM 2.0은 두 가지 핵심 리소스를 정의한다. User와 Group이다.
User 리소스
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "2819c223-7f76-453a-919d-413861904646",
"externalId": "usr-701984",
"userName": "mia@example.com",
"name": {
"formatted": "Mia Kim",
"familyName": "Kim",
"givenName": "Mia"
},
"emails": [
{ "value": "mia@example.com", "type": "work", "primary": true }
],
"active": true,
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "701984",
"department": "Engineering",
"organization": "Backend Platform",
"manager": {
"value": "26118915-6090-4610-87e4-49d8ca9f808d",
"displayName": "Park Junho"
}
}
}
id와 externalId의 구분은 SCIM 설계의 핵심이다.
id는 앱(SCIM 서버)이 발급하고 소유한다. 사용자를 POST로 생성하면 앱이 자신의 내부 식별자로 id를 반환한다. 이 값은 앱마다 다른 형식을 가질 수 있다. IdP는 이 id를 저장해두고, 이후 모든 수정(PATCH/PUT)과 삭제(DELETE) 요청에서 id를 경로에 포함해 해당 리소스를 특정한다.
externalId는 IdP가 자신의 내부 사용자 ID를 앱에 전달하는 채널이다. IdP가 사용자를 생성할 때 이 필드를 자신의 내부 사용자 ID로 채운다. RFC 7643은 앱이 이 값을 그대로 보존할 것을 권고한다(SHOULD). IdP가 앱 id와의 매핑 정보를 어떤 이유로 잃었을 때(DB 마이그레이션, 재연동 등), GET /Users?filter=externalId eq "usr-701984"로 기존 리소스를 찾아 매핑을 재구성할 수 있다. externalId를 채우지 않으면 이 복구 경로가 없다.
userName은 서비스 공급자 내에서 고유한 식별자다. RFC 7643은 이 속성을 REQUIRED로 정의한다. 이메일 주소 형식이 일반적이지만 반드시 이메일일 필요는 없다. 앱 내에서 중복될 수 없고, 계정 검색이나 필터 조회의 기준 값으로 쓰인다.
Group 리소스와 멤버십
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
"displayName": "backend-engineers",
"members": [
{
"value": "2819c223-7f76-453a-919d-413861904646",
"display": "Mia Kim",
"$ref": "https://app.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
]
}
members의 각 항목은 User의 id를 value로 참조한다. IdP가 그룹 멤버십을 관리하는 방식은 앱의 지원 방식에 따라 달라진다. 일부 앱은 User 생성과 동시에 그룹을 속성으로 받는다. 일부는 Group에 PATCH로 멤버를 추가하는 방식만 지원한다. 앱의 스키마(GET /Schemas)와 공식 문서를 확인해 멤버십 관리 방식을 먼저 파악해야 한다.
프로토콜 오퍼레이션 — PATCH의 내부 구조
SCIM 2.0은 /Users와 /Groups 엔드포인트에 표준 HTTP 메서드를 매핑한다. 이 중 실무에서 가장 많은 함정을 만드는 것이 PATCH다.
SCIM PATCH는 JSON Merge Patch(RFC 7396)나 JSON Patch(RFC 6902)를 사용하지 않는다. SCIM은 자체 PatchOp 포맷을 정의한다. schemas에 PatchOp URN을 명시하고, Operations 배열에 각 수정 지시를 담는다.
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
},
{
"op": "replace",
"path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department",
"value": "Security"
},
{
"op": "add",
"path": "emails",
"value": [
{ "value": "mia.kim@corp.example.com", "type": "work", "primary": true }
]
}
]
}
PATCH 연산은 세 종류다.
add: 속성 값을 추가한다. 단일 값 속성이면 기존 값을 덮어쓴다. 다중 값 속성(예: emails)이면 새 항목을 추가한다.
remove: 속성을 제거한다. path에 필터를 조합할 수 있다. path: emails[type eq "work"] 형식으로 지정하면 type이 work인 이메일 항목만 제거한다.
replace: 지정한 속성의 값을 교체한다. 다중 값 속성에 replace를 쓰면 전체 배열을 교체한다. add가 기존 항목을 유지하며 추가하는 반면, replace는 기존 항목 전체를 새 값으로 덮어쓴다. 의도치 않게 replace를 쓰면 기존 이메일 주소나 전화번호 전체가 지워진다.
path의 형식이 직관적이지 않다. 확장 스키마 속성은 전체 URN을 포함한 경로를 써야 한다.
경로 표기가 잘못되면 앱은 400 Bad Request를 반환하거나, 더 나쁜 경우 오류 없이 변경을 무시한다.
Bulk 오퍼레이션 — bulkId 의존성 해결
신규 입사자 온보딩에서 사용자 생성과 그룹 추가를 개별 요청으로 순차 처리하면 두 가지 문제가 생긴다. 첫째, 요청 수가 많아 처리 시간이 길어진다. 둘째, 중간에 실패하면 일부만 처리된 상태가 된다.
Bulk 요청은 여러 오퍼레이션을 하나의 HTTP 요청으로 묶는다. 핵심은 bulkId를 통해 오퍼레이션 간 의존 관계를 선언하는 것이다.
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],
"failOnErrors": 1,
"Operations": [
{
"method": "POST",
"path": "/Users",
"bulkId": "new-mia",
"data": {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "mia@example.com",
"externalId": "usr-new-mia",
"active": true
}
},
{
"method": "PATCH",
"path": "/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a",
"data": {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{
"op": "add",
"path": "members",
"value": [{ "value": "bulkId:new-mia" }]
}]
}
}
]
}
두 번째 오퍼레이션에서 bulkId:new-mia는 아직 생성되지 않은 사용자를 참조한다. 앱은 첫 번째 오퍼레이션을 처리하고 반환된 id로 이 참조를 자동으로 치환한다. 사용자 id를 미리 알지 못해도 의존 관계를 표현할 수 있다.
failOnErrors는 몇 개의 오퍼레이션이 실패했을 때 나머지를 중단할지를 지정한다. 1로 설정하면 첫 번째 오류에서 즉시 중단한다. 0이거나 생략하면 오류가 발생해도 나머지 오퍼레이션을 계속 처리한다. 온보딩처럼 부분 성공이 더 위험한 케이스에서는 failOnErrors: 1이 안전하다.
동기화 생명주기 — 초기 동기화부터 지속 갱신까지
SCIM 연동은 한 번 설정하면 끝이 아니다. 상태를 지속적으로 일치시켜야 한다. 동기화 생명주기를 세 단계로 이해하면 구현 실수를 줄일 수 있다.

초기 동기화(Initial Sync): 연동을 처음 설정할 때다. IdP의 사용자·그룹 전체를 앱에 내보내야 한다. 주의할 점은 앱에 이미 사용자가 있을 수 있다는 것이다. 회사가 먼저 앱을 쓰다가 나중에 IdP를 도입하는 경우가 많다. 앱의 기존 사용자와 IdP 사용자를 어떻게 연결할지(userName으로 매핑할지, externalId로 매핑할지, 수동으로 연결할지)를 먼저 결정해야 한다. 이 결정 없이 초기 동기화를 시작하면 기존 사용자와 새로 생성된 중복 계정이 함께 존재하는 상태가 된다.
증분 동기화(Incremental Sync): 변경이 발생할 때마다 IdP에서 앱으로 SCIM 요청을 보내는 방식이다. IdP에서 사용자 속성이 바뀌면 PATCH, 사용자가 그룹에 추가되면 Group PATCH, 퇴사 처리가 되면 User PATCH(active=false) 또는 DELETE가 발생한다. 이벤트 기반이라 빠르지만, 앱이 일시적으로 다운됐을 때 발생한 이벤트가 유실되지 않도록 보장하는 방식(큐, 재시도 정책)이 필요하다.
조정(Reconciliation): 주기적으로 IdP와 앱의 전체 상태를 비교하는 작업이다. 증분 동기화에서 누락된 변경, 앱 측에서 SCIM을 거치지 않고 직접 발생한 변경(예: 관리자가 앱 UI에서 직접 수정)을 감지한다. GET /Users로 앱의 전체 사용자 목록을 가져와 IdP 목록과 diff를 내고, 불일치 항목을 보정한다. 조정 주기는 비즈니스 리스크에 따라 다르다. 퇴사 계정 누락이 가장 위험하다면 매일 조정을 돌리는 것이 타당하다.
구현 시 주의해야 할 함정들
스펙은 정확하지만 구현 현실은 복잡하다. 자주 발생하는 패턴을 알고 있으면 디버깅 시간을 크게 줄일 수 있다.
PUT vs PATCH — 전체 교체의 부작용
PUT은 리소스를 통째로 교체한다. IdP가 SCIM 코어 스키마에 정의된 표준 속성만 PUT에 포함하면, 앱이 자체적으로 저장하던 확장 속성이 지워진다. Slack의 경우 프로필 커스터마이징 필드, Salesforce의 경우 CRM 관련 설정값이 여기에 해당한다. PATCH는 지정한 속성만 건드리므로 이 문제가 없다. 그러나 모든 앱이 PATCH를 구현하지는 않는다. PUT을 사용해야 하는 앱이라면 GET으로 현재 상태를 먼저 읽고, 앱의 기존 커스텀 속성을 포함한 상태로 PUT을 구성할 수 있다. 이는 GET-then-PUT 패턴으로 요청 수가 두 배가 된다.
ETag — 낙관적 동시성과 관리자 수동 편집 충돌
ETag는 앱이 리소스마다 발급하는 버전 토큰이다. 리소스가 수정될 때마다 앱이 새 ETag를 발급한다. GET 응답에 ETag: "a3f8bc" 헤더가 포함된다. IdP가 PUT이나 PATCH 요청에 If-Match: "a3f8bc"를 함께 전송하면, 앱은 "내가 마지막으로 읽었을 때와 지금 버전이 같은가"를 확인한다. 다른 시스템이 그사이 리소스를 수정했다면 앱은 412 Precondition Failed를 반환한다.
실무에서 ETag 충돌이 가장 자주 발생하는 경로는 SCIM을 우회한 직접 수정이다. Salesforce 관리자가 Salesforce 자체 관리 화면에서 사용자 속성을 바꾸는 경우를 생각해보자. SCIM을 거치지 않고 Salesforce 내부 API를 통해 직접 수정한 것이므로 IdP는 이 변경을 모른다. Salesforce는 리소스가 바뀌었으므로 ETag를 갱신한다

여기서 IdP가 선택할 수 있는 경로가 두 가지다.
첫째, 412를 무시하고 If-Match 없이 재시도한다. 요청은 성공하지만 관리자가 Salesforce UI에서 직접 수정한 내용이 IdP 값으로 덮어씌워진다.
둘째, GET으로 현재 ETag를 다시 조회한 뒤 재시도한다. 결국 IdP가 덮어쓴다.
이 문제의 근본 원인은 "누가 사용자 속성의 권위를 갖는가"가 정해지지 않은 것이다. IdP를 SSOT(single source of truth)으로 지정하고, Salesforce UI에서의 직접 수정을 운영 정책으로 차단하는 것이 근본 해결책이다. ETag 충돌 처리 로직을 정교하게 만드는 것보다, 이 설계 판단이 선행돼야 한다.
active=false vs DELETE — 비활성화와 삭제의 차이
SCIM은 계정을 처리하는 두 방법을 구분한다. active 속성을 false로 설정하는 PATCH 요청은 계정을 비활성화하지만 리소스는 유지한다. DELETE는 리소스 자체를 제거한다.
많은 엔터프라이즈 앱이 DELETE를 구현하지 않는다. 이유는 두 가지다. 리소스가 삭제되면 그 리소스와 연결된 모든 데이터(작업 이력, 감사 로그, 연결된 레코드)를 어떻게 할지 복잡한 결정이 필요하다. 그리고 퇴사자 감사 추적을 위해 계정 기록을 보존해야 하는 경우가 많다.
IdP가 DELETE를 보냈는데 앱이 405 Method Not Allowed를 반환하면 계정은 그대로 남는다. 더 위험한 것은 일부 앱이 405 대신 204를 반환하면서도 실제로는 삭제하지 않는 경우다. IdP는 삭제 성공으로 기록하지만 앱에는 활성 계정이 존재한다.
RFC 7643이 정의하는 ServiceProviderConfig 스키마에는 DELETE 지원 여부 필드가 없다. DELETE 지원 여부는 앱 공식 문서에서 확인하거나, 테스트 환경에서 DELETE 요청을 보내 응답 코드로 판별해야 한다. DELETE가 없으면 IdP 연동 설정에서 active=false를 명시적으로 기본 비활성화 방식으로 지정해야 한다. 그리고 조정(Reconciliation) 주기에서 IdP에서 비활성화 처리됐지만 앱에는 여전히 활성 상태로 남아 있는 계정을 탐지하는 검사를 포함해야 한다.
externalId 매핑 소실과 복구
IdP는 사용자를 POST로 생성하면 앱이 반환한 id를 내부적으로 저장한다. 이 매핑 테이블이 DB 마이그레이션, 연동 재설정, 재해 복구 등의 이유로 사라지면 IdP는 기존 사용자를 찾지 못한다.
다음 동기화 사이클에서 IdP는 각 사용자가 앱에 존재하는지 확인한다. 매핑이 없으면 새 사용자로 간주하고 POST를 시도한다. 앱이 userName 또는 externalId 기반으로 중복을 감지하면 409 Conflict를 반환한다. IdP가 이 응답을 처리하지 못하면 프로비저닝이 멈춘다. 모든 사용자에 대해 이 오류가 반복되면 수백 개의 409가 발생하고 연동이 중단된다.
GET /Users?filter=externalId eq "usr-701984"로 기존 리소스를 찾아 매핑을 재구성하는 것이 복구 경로다. 이 경로가 동작하려면 두 가지 선행 조건이 있다. 첫째, 최초 프로비저닝 때 externalId가 채워져 있어야 한다. 둘째, 앱이 externalId 기반 Filter를 지원해야 한다. 이 두 조건을 연동 설계 시점에 확인하지 않으면 복구 경로가 없는 상태로 운영하게 된다.
레이트 리밋과 백프레셔
대규모 초기 동기화나 대규모 계정 변경 이벤트(구조조정, 부서 통폐합) 시 SCIM 요청이 폭증한다. 대부분의 SaaS 앱은 SCIM 엔드포인트에도 레이트 리밋을 적용한다. 429 Too Many Requests 응답에는 대개 Retry-After 헤더가 포함된다.
IdP가 429를 무시하고 요청을 계속 보내면 모든 요청이 거부되고 동기화가 완전히 실패한다. 지수 백오프(exponential backoff)와 지터(jitter)를 조합한 재시도 전략이 필요하다. 그리고 레이트 리밋이 발생했다는 것 자체를 모니터링 알림으로 올려야 한다
Bulk 요청은 레이트 리밋 관점에서 유리하다. 100개의 개별 요청 대신 Bulk 1개 요청으로 처리하면 앱 측에서 카운트하는 요청 수가 줄어든다. Bulk를 지원하는 앱이라면 대량 처리에 Bulk를 우선으로 사용하는 것이 좋다.
프로비저닝 전체 흐름
입사부터 퇴사까지 SCIM이 어떻게 개입하는지를 시퀀스로 정리하면 이렇다.

이 다이어그램에서 IGA와 IdP의 역할은 명확히 분리된다. IGA는 이 역할을 가진 직원은 어떤 그룹에 속해야 하는가, 퇴사 시 어느 시스템의 접근을 먼저 차단해야 하는가와 같은 "무엇을 해야 하는가"를 결정한다. IdP와 SCIM은 그 결정을 실제로 앱에 전달하는 실행 레이어다. 정책과 집행을 분리하는 구조다.
구현 판단 기준
SCIM 서버를 구현하는 쪽(앱)에서: ServiceProviderConfig를 정직하게 채워야 한다. 지원하지 않는 기능을 지원한다고 표시하면 클라이언트 측에서 예상치 못한 오류가 발생한다. 지원 범위를 결정할 때 권고 순서가 있다. 먼저 PATCH를 구현한다 — PUT과 달리 변경된 속성만 전송하므로 기존 데이터를 덮어쓸 위험이 없고, IdP(클라이언트)가 GET-then-PUT 패턴을 쓸 필요도 없어진다. 그 다음 SCIM Filter를 구현한다. IdP가 조건을 지정해 특정 사용자 집합만 조회할 수 있어 조정(Reconciliation) 작업의 범위를 좁히고 효율을 높일 수 있다. DELETE를 구현하기 어렵다면 active=false 방식만 지원하고 앱 문서에 명시한다.
IdP(또는 IGA)가 연동을 설정할 때: 연동 전 GET /ServiceProviderConfig와 GET /Schemas를 읽고 앱의 실제 능력을 파악하는 것이 시작점이다. Bulk가 없으면 요청 순서와 재시도 전략을 직접 설계해야 한다. ETag가 있으면 앱 UI에서의 직접 수정을 운영 정책으로 금지하거나, ETag 충돌 처리 로직을 명시적으로 구현해야 한다. externalId는 생성 시점부터 반드시 채운다.
SCIM이 해결하지 못하는 것: SCIM은 전송 표준이다. "언제, 어떤 속성으로, 어떤 그룹에 넣어야 하는가"라는 비즈니스 정책은 IGA가 결정한다. 앱이 지원하지 않는 커스텀 속성 관리, 앱마다 다른 권한 모델(Salesforce의 Permission Set, GitHub의 Repository-level 권한 등)은 SCIM 스코프 밖이다. SCIM이 처리하는 범위는 사용자 존재 여부와 그룹 멤버십이다. 앱 내부의 세밀한 권한 설정은 별도 자동화 또는 수동 관리가 필요하다.
운영하다 보면 SCIM을 지원하지 않는 레거시 앱이나 인하우스 시스템이 반드시 남는다. 이 앱들에는 SCIM이 아니라 앱 자체 API를 사용하는 커스텀 프로비저닝 코드가 필요하다. 표준 연동과 커스텀 연동을 함께 운영할 때는 프로비저닝 결과 모니터링과 조정 방식을 하나로 통일해야 한다. 앱별로 "계정이 실제로 비활성화됐는가"를 확인하는 검증 로직이 없으면 감사에서 발견될 때까지 누락을 모른다.
'Network > Zero Trust' 카테고리의 다른 글
| [Zero Trust - Device] Device Posture: 신원은 사람을 증명하지만 기기는 증명하지 않는다 (0) | 2026.03.24 |
|---|---|
| [Zero Trust - IAM] PAM과 JIT Access: 자격증명이 수명을 가지면 무엇이 달라지는가 (0) | 2026.03.23 |
| [Zero Trust - IAM] IAM과 IGA: Privilege Creep이 생기는 이유와 접근 권한 거버넌스 설계 (0) | 2026.03.22 |
| [Zero Trust - IAM] Google Zanzibar: 2조 개의 권한을 일관성 있게 10ms에 처리하는 설계 (0) | 2026.03.20 |
| [Zero Trust - IAM] RBAC의 구조적 한계와 ABAC·ReBAC의 설계 차이 (0) | 2026.03.19 |