为什么不能原样转交收到的令牌
한국어 원문으로 표시합니다.
한 줄 요약
서비스가 하류 서비스를 부를 때 쓰는 토큰은 두 갈래다. 사용자와 무관한 일이면 서비스 자기 이름으로 받은 client credentials 토큰을 쓰고, 사용자를 대신하는 일이면 받은 사용자 토큰을 STS 에 내고 "이 하류 전용, 누가 대신 부르는지 적힌" 새 토큰으로 바꿔 쓴다(토큰 교환). 받은 토큰을 그대로 흘려보내는 것은 둘 다 아니다.
왜 이게 필요했나
주문 API 가 사용자 토큰(aud=lab-api)을 받고, 그 사용자를 위해 재고·결제 서비스를 부른다고 하자. 가장 쉬운 길은 받은 토큰을 그대로 Authorization 에 실어 넘기는 것이다. 이 지름길에는 문제가 셋 있다.
첫째, 그 토큰의 aud 는 하류가 아니다. 하류가 받아 주려면 aud 검사를 꺼야 하고, aud 를 안 보는 서비스는 다른 서비스용 토큰도 받는다. 한 곳에서 샌 토큰이 체인 전체의 열쇠가 된다. 둘째, 하류는 사용자가 직접 불렀는지 서비스가 대신 불렀는지 구분하지 못한다. 감사 로그에 행위자가 남지 않는다. 셋째, 권한이 줄지 않는다. 사용자 토큰의 권한이 체인 끝까지 그대로 간다.
반대로 서비스 자기 토큰만 쓰면 하류는 "api-svc 가 불렀다" 만 알고 누구를 위한 요청인지 모른다. 사용자별 권한 판단을 할 수 없다. 이 둘 사이를 메우는 것이 RFC 8693 OAuth 2.0 Token Exchange 다.
어떻게 동작하나
client credentials. RFC 6749 §4.4 는 이 grant 를 클라이언트가 자기 통제 아래의 자원에 접근할 때 쓰라고 하고, 기밀 클라이언트만 쓸 수 있다고 못박는다. 응답에는 refresh token 을 넣지 않는 것이 권장된다(§4.4.3) — 비밀을 쥔 클라이언트는 언제든 다시 받으면 되기 때문이다. 이 실습의 Keycloak 에서 api-svc 로 받으면 사용자 대신 service-account-api-svc 라는 서비스 계정이 주체가 된다.
토큰 교환. 요청은 토큰 엔드포인트에 이렇게 보낸다.
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<사용자 토큰> subject_token_type=...:token-type:access_token (필수)
actor_token=<서비스 토큰> actor_token_type=...:token-type:access_token (선택)
audience=orders-api (선택)
RFC 는 두 의미를 구분한다. impersonation 은 A 가 B 와 구별되지 않게 B 가 되는 것이고, delegation 은 A 가 자기 신원을 유지한 채 B 를 대리하는 것이다. 위임이면 새 토큰에 act 클레임으로 현재 행위자를 남긴다. 중첩된 act 는 이전 행위자들이고, RFC 는 접근 제어에 최상위 클레임과 가장 바깥 act 만 쓰고 나머지는 정보로만 보라고 한다. 응답에는 issued_token_type 이 필수다. subject_token 이 무효면 invalid_request, 요청한 audience 로는 발급할 수 없으면 invalid_target 을 돌려준다.
책임은 둘로 나뉜다. STS 는 subject_token 을 서명·iss·exp·aud 까지 검증하고, audience 를 허용 목록과 대조하고, 권한을 줄이고, 수명을 짧게 준다. 검증을 빼먹은 STS 는 위조 토큰을 자기의 진짜 서명으로 세탁해 주는 기계가 된다. 하류는 aud 가 자기인지, act 가 허용된 호출자인지 본다.
현장에서 만나는 모습
"Keycloak 으로 토큰 교환 하면 되지" 는 버전을 먼저 봐야 하는 말이다. Keycloak 은 26.2 에서 표준 토큰 교환을 정식 지원하기 시작했다. token-exchange 문서에 따르면 표준 교환(V2)은 서버를 켜면 기본으로 활성화되고, 레거시 교환(V1)은 preview 이자 폐기 예정이라 --features=token-exchange 같은 플래그와 fine-grained admin permissions v1 을 함께 켜야 한다. V2 도 같은 렐름 안의 클라이언트끼리 교환하는 경우만 지원하고, 사용자 impersonation 과 외부 토큰 교환은 지원하지 않는다. 위임(delegation)은 문서에서 실험 기능으로 분류돼 있다.
이 실습 파드의 Keycloak 은 26.0.7 이다. 즉 V2 가 없다. 실제로 discovery 문서의 grant_types_supported 에 token-exchange 가 없고, 그 grant 로 요청하면 unsupported_grant_type 이 돌아온다. 그래서 이 실습은 service-to-service 는 Keycloak 의 client credentials 로 실측하고, 교환은 STS 를 직접 만들어 재현한다. 현장에서도 같은 판단을 하게 된다 — 업그레이드할 것인가, preview 기능에 운영을 걸 것인가, 교환 창구를 따로 둘 것인가.
다음 실습에서 할 것
Keycloak 에서 api-svc 의 서비스 토큰을 받아 서명과 클레임을 확인하고, Keycloak 26.0.7 이 토큰 교환을 거절하는 것을 직접 본다. 그다음 8308 에 로컬 STS 를 세워 dev1 의 토큰을 orders-api 전용 토큰(act 에 api-svc 기록)으로 바꾸고, 8309 의 하류가 aud 와 act 를 검사하게 한다. 마지막으로 위조·변조·만료된 subject_token 과 모르는 audience 가 모두 거절되는지 실제 요청으로 증명한다.