요약

  • LACNIC이 공개한 PAI 클라이언트의 권한 캐시 유효기간은 2분입니다. 외부 예외 처리 조건을 충족하는 갱신 실패 후 오래된 항목은 시간을 다시 계산할 수 있고, 원래의 권한 객체는 그대로 유지됩니다.
  • 함께 제공된 테스트용 잘못된 형식의 JSON은 오래된 인증된 객체가 반환될 것을 명시적으로 기대합니다. 정상적으로 디코딩된 새로운 거부 응답은 새 항목을 만들며 이 폴백 시나리오에 속하지 않습니다.
  • 소스 코드는 이 경로가 프로덕션에서 사용되고 있음을 증명하지 않으며, 이미 철회된 권한이 계속 유효하다는 것도 증명하지 않습니다. 증명할 수 있는 것은 방금 갱신된 캐시가 반드시 방금 확인된 권한을 담고 있지는 않다는 점입니다.

방금 갱신된 것이 곧 방금 확인된 것은 아니다

권한 결과가 방금 서버에서 가져온 것이라면 사용자는 그것이 언제 가져온 것인지 물을 수 있습니다. 캐시가 응용 프로그램에 의해 방금 시간이 재계산되었다면 사용자는 이 컨테이너가 언제 터치되었는지만 알 수 있습니다. 이 두 가지는 자주 함께 발생하므로 같은 것으로 쉽게 간주됩니다. 경계를 진정으로 시험하는 것은 새 응답을 이해하지 못했는데 컨테이너만 다시 젊어진 경우입니다.

여기서 새로운 인가 결정이 이루어진 것은 아니며, 응용 프로그램은 여전히 이전 답변에 의존하여 작동합니다. 일시적 장애를 합리적으로 견디고 있을 수도 있고, 비즈니스가 허용할 수 있는 오래된 답변의 나이를 이미 초과했을 수도 있습니다. 두 경우는 "캐시가 아직 만료되지 않았다"는 이유만으로 구분할 수 없습니다. 만료 규칙은 기준점이 가장 최근의 신뢰할 수 있는 응답인지, 아니면 오래된 컨테이너에 대한 가장 최근의 갱신인지 명시해야 합니다.

이 구분은 LACNIC이 공개한pai-auth-ws-client에서 나타납니다. 이번 검토는 2026년 9월 14일에 관찰된 저장소의 main 브랜치에 있는 커밋b85523718bfdbd834c85add8e06c3b4a1b813e59로 고정되어 있습니다. 커밋을 고정하면 독자가 동일한 구현을 재검토할 수 있으며, 이것이 어떤 프로덕션 시스템이 실행 중인 버전이라는 의미는 아닙니다.

프로젝트 README는 이를 PAI 인증, 권한 부여 및 세션 관련 기능을 제공하는 Java 클라이언트로 설명합니다. 문서는 프로젝트의 목적을 파악할 수 있지만 클라이언트 설치 현황 조사는 제공하지 않습니다. 공개 프로젝트에서 "모든 LACNIC 애플리케이션이 이 경로를 사용한다"로 넘어갈 수 없으며, 소스 코드에 한 분기가 존재한다는 사실에서 실제로 권한 남용이 발생했다는 결론으로 넘어갈 수도 없습니다.

2분은 캐시 유효기간이지 권한 수명이 아니다

PortalWSClient는 권한 결과를 저장하기 위해 정적이고 프로세스 내부적인ConcurrentHashMap을 사용합니다. 키는 메서드에 전달된 원시 토큰이고, 항목 안에는TokenData객체와 수정 가능한 타임스탬프가 있습니다.CACHE_DURATION_MS는 2분의 유효기간을 설정하며, 만료 여부는 현재 시각과 이 타임스탬프의 차이로 판단합니다.

항목이 만료되지 않았다면 메서드는 이미 저장된 데이터를 직접 반환하며, 이 경로는/authorization의 새 응답을 다시 가져오지 않습니다. 이것이 캐시의 일반적인 용도입니다. 원격 호출을 줄이고, 모든 비즈니스 요청이 의존 서비스의 대기 시간과 일시적 장애를 똑같이 겪지 않도록 합니다. 이러한 정상적인 동작만으로 인가 시스템이 잘못 설계되었다고 주장할 수는 없습니다. 문제는 어떤 동작이 이 "아직 만료되지 않음"의 기준점을 재설정할 권리를 갖는가입니다.

항목이 만료되면 메서드는 공유 맵에서 그 항목을 삭제한 후 새 결과를 가져와 디코딩하려고 시도합니다. 그러나 현재 메서드의 지역 변수에는 여전히 그 오래된 항목에 대한 참조가 남아 있습니다. 맵에서 제거했다는 것이 이번 실행이 오래된 객체를 잃었다는 의미는 아닙니다. 이 세부 사항은 이후 예외 처리가 여전히 사용할 수 있는 무언가를 남겨두며, 연속 작동 메커니즘의 실질적인 지점입니다.

새 응답이 성공적으로 디코딩되면 메서드는 새 데이터를 새 캐시 항목에 넣고 반환합니다. 성공적인 디코딩이 반드시 접근을 허용한다는 의미는 아닙니다. 정상적으로 읽을 수 있는 부정적 권한 데이터도 새 항목의 경로를 따라 처리됩니다. 따라서 "클라이언트가 최신 거부를 받은 후에도 여전히 이전 인가를 고수한다"는 것은 이 증거가 지지하는 결론이 아닙니다. 거부를 이해한 것과 응답을 이해하지 못한 것은 반드시 별도로 논의해야 합니다.

외부IOException처리가 이 문서가 주목하는 분기입니다. 여전히 오래된 항목에 대한 참조가 있다면 메서드는cached.extend()를 호출하여 해당 항목의 타임스탬프를 현재 시각으로 변경하고, 맵에 다시 넣고, 이전에 저장한 객체를 반환합니다. 이 단계는 새로운, 성공적으로 디코딩된 인가 결정을 얻은 것이 아니라, 이전 결정을 담고 있는 컨테이너의 시간 기준점을 변경한 것입니다.

그 결과 캐시는 다시 2분의 창을 얻지만, 원래의 권한 응답은 새로운 확인을 한 번도 받지 못합니다. 만료 후 다음 갱신에서 다시 조건에 부합하는 실패가 발생하면 같은 분기가 다시 시간을 연장할 수 있습니다. 이 단일 클래스에는 원래 성공 응답 시간을 보존하는 불변 필드가 없고, 그 응답부터 절대 나이 상한도 없으며, 갱신 횟수를 제한하는 카운터도 없습니다.

이 결함은 이 클래스 정의에 대한 설명이지 전체 애플리케이션 아키텍처에 대한 판결이 아닙니다. 호출자는 다른 곳에서 나이를 기록하고, 유예 기간을 제한하고, 민감한 작업 전에 다른 권위 있는 검증을 요구할 수 있습니다. 소스 코드는 이러한 외부 보호가 존재함을 증명할 수도 없고 존재하지 않음을 증명할 수도 없습니다. 말할 수 있는 것은 이 캐시 자체의 2분이 오래된 권한 데이터가 최대 얼마나 오래될 수 있는지를 자동으로 답하지 않는다는 것입니다.

테스트가 적극적으로 오래된 객체를 보호했다

함께 제공된PortalWSClientTest는 구현 의도를 더 명확하게 합니다.testGetTokenDataReturnsCachedDataWhenRefreshFails라는 테스트는 먼저 모의 HTTP 응답으로 긍정적 인증 샘플을 만들고, 캐시 타임스탬프를 인위적으로 5분 전으로 설정한 다음, 두 번째 모의 호출에 잘못된 형식의 JSON 응답을 제공합니다.

테스트는 동일한 인증된 객체가 반환되고 두 번의 모의 HTTP 실행이 발생할 것을 단언합니다. 이는 "프로덕션에서 사용자가 5분 후에도 여전히 접근할 수 있다"는 보고가 아닙니다. 5분은 테스트가 적극적으로 작성한 오래된 시간이며, HTTP 응답도 모의입니다. 이 글의 검토는 테스트 소스를 읽었으며 테스트를 실행하지 않았고, 프로덕션 인증 엔드포인트에 접근하지 않았으며, 실제 자격 증명을 사용하지도 않았습니다.

이런 제한에도 불구하고 테스트는 중요한 긍정적 정보를 제공합니다. 이 갱신 실패 후 오래된 객체가 살아남는 것은 독자가 예외 처리 코드 조각에서 우연히 추측한 결과가 아니라 명시적으로 테스트에 의해 보호되는 연속 작동 동작입니다. 평가하려면 이것이 실제 문제를 해결하고 있음을 먼저 인정하는 것이, 바로 권한 남용 라벨을 붙이는 것보다 가치가 있습니다.

권한 서비스가 일시적으로 읽을 수 없다는 것이 모든 여전히 합법적인 세션이 즉시 종료되어야 한다는 의미는 아닙니다. 접근을 일괄 차단하면 범위가 좁은 의존 장애가 비즈니스 정지로 확대될 수 있습니다. 제한된 유예 작동을 허용하면 이러한 확대를 줄일 수 있습니다. 합리적인 비판은 "유예"가 어떻게 계산되고 누가 판단할 수 있으며 어떤 작업이 계속될 수 있는지에 집중해야 하며, "어떤 오래된 답변도 다시 사용될 수 없다"에 집중해서는 안 됩니다.

동일한 코드는 연장된 캐시를 일시적으로 사용한다는 경고를 기록합니다. 이 반증을 무시하고 동작을 완전히 보이지 않고 관찰 지점이 없는 것으로 묘사할 수는 없습니다. 운영 팀이 경고를 모니터링할 수도 있습니다. 더 좁은 질문은 이 경고가 원래 성공 응답의 나이를 호출자에게 반환되는 권한 객체에 포함하지 않는다는 점이며, 경고가 한 번 나타났다는 사실만으로 오래된 데이터가 얼마나 지속되었는지 추산할 수 없다는 점입니다.

권한을 반환하는 사람은 시간을 함께 넘겨주지 않는다

TokenData에는 인증 상태, 토큰, 역할, 오류 필드 및ipAllowed가 포함되어 있습니다. 이 데이터 객체는 "마지막 성공 응답 시간"을 공개하지 않고, 신선/유예/불가 상태 분류도 없으며, 현재 값이 오래된 캐시 폴백에서 왔다는 설명도 없습니다. 이러한 필드만 보면 호출자는 이 객체가 원격에서 방금 가져온 것인지 아니면 방금 젊은 캐시에 다시 저장된 것인지 알 수 없습니다.

이것이 토큰 자체가 영원히 만료되지 않는다는 의미는 아닙니다. 토큰의 암호학적 유효기간, 캐시의 현재 창, 발급자의 현재 역할 상태는 서로 다른 세 가지 명제입니다. 유효기간이 끝나지 않은 토큰이 역할 할당이 변경된 적이 없음을 자동으로 증명하지 않으며, 아직 만료되지 않은 캐시가 원격 검증이 방금 발생했음을 자동으로 증명하지도 않습니다. 세 가지를 "유효한 권한"으로 묶으면 실제로 증명해야 할 연결이 생략됩니다.

주변 애플리케이션은 토큰 만료 시간을 확인하거나, 원본 IP 제한을 적용하거나, 자체 성공 응답 시간을 저장하거나, 중요한 쓰기 작업 전에 권한을 다시 확인할 수 있습니다. 이러한 방식은 모두 실제 결과를 바꿀 수 있습니다. 리스크를 만들기 위해 이것들을 아키텍처에서 제거할 수 없고, 의문을 해소하기 위해 무에서 유로 추가할 수도 없습니다. 올바른 경계는 클라이언트가 명시적으로 반환한 것과 애플리케이션이 아직 검증해야 할 것을 분리하는 것입니다.

또한 "성공 응답"이 정확히 무엇을 의미하는지 물어야 합니다. 공개된PortalHttpClient는TrustAllStrategy와NoopHostnameVerifier로 HTTP 클라이언트를 구성합니다. 이 보조 구성 요소에서 URL이 HTTPS를 나타내고 JSON이 디코딩될 수 있다는 것만으로는 응답 발급자의 신원을 암호학적으로 검증했다는 것을 단독으로 증명할 수 없습니다.

이것은 소스 코드에 국한된 또 다른 관찰이며, 프로덕션 TLS 감사도, 가로채기·공격·유출 증거도 아닙니다. 시간 문제와의 연결은 구체적입니다. 설계가 "가장 최근 신뢰할 수 있는 응답의 시간"을 저장하려면, 어떤 출처 인증된 응답이 그 시간을 재설정할 권한을 갖는지도 정의해야 합니다. 디코딩 가능한 바이트에 정확한 날짜를 부여한다고 해서 출처 신뢰성이 자동으로 보충되지 않습니다.

형식 오류가 모든 장애의 공통 이름은 아니다

테스트가 보여주는 것은 형식 오류 응답 경로이며, 포괄적인 네트워크 장애 보장이 아닙니다.readUrlToken은 비교적 광범위하게 예외를 잡아 null을 반환할 수 있으며, 외부에서 연장 가능한 처리는IOException을 잡습니다. 빈 응답, 연결 오류, 형식 오류 JSON은 해당 경로를 실행하고 검증하지 않고서는 동일한 동작으로 기술되어서는 안 됩니다.

따라서 "인가 서비스가 장애가 나면 클라이언트는 반드시 계속 작동한다"는 연속 작동 보장을 과도하게 확대한 것이고, "모든 장애가 이미 철회된 접근을 보존한다"는 리스크를 과도하게 확대한 것입니다. 두 문장 모두 제한된 소스 코드 증거를 특정 토큰, 애플리케이션 제어, 프로덕션 환경에 대한 포괄적 주장으로 바꿉니다. 듣기에는 힘이 있지만 독자가 영향을 판단하는 데 필요한 변수를 지워버립니다.

유지될 수 있는 결론은 다음과 같습니다. 함께 제공된 테스트의 형식 오류 응답 시나리오는 오래된 객체가 반환될 것을 기대하며, 해당 폴백은 캐시 항목의 시간을 재설정합니다. 이 클래스에는 반복되는 적격 폴백에 대해 원래 성공 응답부터 계산되는 별도의 절대 상한이 없습니다. 실제 권한 작업에 영향을 미치는지 여부는 설치된 버전, 발급자 의미론, 호출자의 추가 검증에 여전히 달려 있습니다.

마찬가지로 저장소의 새 커밋 날짜를 리스크 시작 날짜로 취급할 수 없습니다. 공개 버전이 언제 출시되었는지, 어떤 애플리케이션이 언제 채택했는지, 어떤 계정이 언제 권한을 변경했는지는 서로 다른 사건입니다. 이번에는 재검토용 코드 버전을 고정했을 뿐 배포 타임라인을 확립하지 않았습니다. 이러한 날짜 혼용을 피하는 것과 두 개의 캐시 시계를 혼용하지 않는 것은 같은 증거 원칙을 따릅니다.

절대 유예를 검증하려면 검증할 명제를 관찰 가능한 질문으로 작성해야 하며, 오래된 객체가 반환되었는지만 물어서는 안 됩니다. 한 번의 폴백 후 원래 성공 응답 시간이 여전히 변하지 않았는가? 다시 폴백한 후에도 여전히 동일한 기준점에서 상한을 계산하는가? 상한에 도달하면 호출자가 정상 거부, 의존 불가, 로컬에서 더 이상 오래된 데이터를 받지 않음을 구별할 수 있는가? 이것들은 제안된 검증 질문이며 이번에 실행된 테스트 결과가 아닙니다.

추가로 하나의 대조가 필요합니다. 갱신이 올바르게 형식화된 부정적 데이터를 얻으면 시스템은 새 응답 경로를 따라 작동해야 하며 장애 유예 서사를 계속 사용해서는 안 됩니다. 기존 소스는 새 항목 경로를 명시적으로 제공하므로 대조를 생략해서는 안 됩니다. 거부를 장애로 취급하면 발급자가 이미 준 답변이 흐려지고, 읽을 수 없음을 거부로 취급하면 정지가 확대될 수 있습니다. 상태 설계의 가치는 두 가지가 서로를 사칭하지 않도록 하는 데 있습니다.

이 검증 방법은 "동일한 객체가 반환된다"는 것이 왜 제한된 증거인지도 설명합니다. 동일한 객체는 합리적인 단기 연속 작동에 사용될 수도 있고, 주변 애플리케이션이 독립적인 제한을 추가할 수도 있습니다. 관찰해야 할 것은 결정이 어떻게 형성되는지입니다. 호출자가 객체를 받은 후 나이를 인식하는지, 토큰을 검증하는지, 작업별로 다른 확인을 요구하는지입니다. 객체가 동일하다는 것만으로는 최종 비즈니스 작업이 승인되었다고 역추론할 수 없으며, 어떤 권한 없는 작업이 발생했다고 역추론할 수도 없습니다.

유예는 존재할 수 있지만 시작과 끝이 있어야 한다

더 명확한 인터페이스는 두 개의 시간을 저장합니다. 마지막으로 출처 인증되고 성공적으로 디코딩된 인가 응답의 시간, 그리고 캐시가 마지막으로 터치된 시간입니다. 오래된 항목을 계속 사용하는 것은 후자만 변경할 수 있고 전자를 조용히 다시 쓸 수는 없습니다. 호출자가 받는 결과도 신선한 데이터, 유예 데이터, 불가 상태를 명시적으로 구별할 수 있어야 하며, 긍정적인 인증 부울값에서 연속 작동의 성격을 추측할 필요가 없어야 합니다.

유예 상한은 신뢰할 수 있는 응답 순간부터 절대적으로 계산될 수 있습니다. 이렇게 하면 각 실패 후 캐시를 한 번 터치하는 것으로 유예 기간을 계속 앞당길 수 없습니다. 상한은 작업별로 구별할 수도 있습니다. 철회 가능한 읽기 작업과 관리자 수정, 다른 사람에게 권한 부여, 되돌릴 수 없는 명령 발행은 반드시 같은 수준의 오래된 답변을 받아들일 필요는 없습니다. 이것들은 분석용 의사결정 범주이며, 이 경로를 사용한다고 확인된 LACNIC 기능 목록이 아닙니다.

로그는 폴백 횟수, 응답 나이, 선언된 저하 모드를 연결할 수 있지만 원래 토큰이나 전체 세션을 기록할 필요는 없습니다. 신뢰할 수 있는 응답을 다시 얻으면 유예 상태는 끝나야 하며, 이해한 새로운 거부는 여전히 거부를 유지해야 합니다. 모니터링의 목적은 오래된 답변이 왜 여전히 수용되었는지 설명하는 것이지, 감사를 위해 자격 증명을 더 많은 위치로 복사하는 것이 아닙니다.

이 권고는 LACNIC이 이미 공개적으로 약속한 수정이 아니며, 전체 주변 제어에 대한 결론도 아닙니다. 재검토 가능한 구현 세부 사항을 더 명확한 인터페이스 요구 사항으로 변환한 것일 뿐입니다. 2분의 캐시 유효기간은 정상적인 엔지니어링 선택일 수 있지만, 다른 시계 없이는 권한 결정의 최대 나이로 취급될 수 없습니다.

유예에 끝점을 설정한다고 해서 모든 의존성 흔들림을 사용자에게 오류로 표시해야 하는 것은 아닙니다. 애플리케이션은 명확한 허용 범위 내에서 불필요한 반복 요청을 피할 수 있습니다. 변화는 연속 작동을 암시적 결과에서 설명 가능한 정책으로 만드는 것입니다. 발급자가 방금 권리를 재확인했다고 주장하는 것이 아니라, 일시적으로 이전 답변을 수용하고 있음을 인정하는 것입니다. 그래야 비즈니스 소유자가 가용성 이득과 다양한 작업의 결과를 평가할 수 있으며, 여전히 긍정적인 인증 필드 하나만 보는 대신 볼 수 있습니다.

마찬가지로 불가를 표시하는 것이 권한을 철회하는 것은 아닙니다. 불가는 현재 검증 능력에 관한 상태이고, 거부는 발급자의 결정에 관한 상태이며, 유예는 애플리케이션이 일시적으로 어떤 위험을 감수하는지에 관한 상태입니다. 세 가지를 명확히 표현해야 신뢰할 수 있는 응답이 복구된 후 정상 경계로 돌아갈 수 있으며, 후속 검토에서 당시 누가 어떤 규칙에 따라 계속 작동했는지 판단할 수 있습니다. 캐시는 데이터를 저장할 수 있지만 이러한 서로 다른 판단을 위해 공통 의미를 골라주어서는 안 됩니다.

이것은 운영 인수인계 시 남겨야 할 설명이기도 합니다. 다음 교대 팀은 젊은 타임스탬프만으로 의존 서비스가 복구되었다고 추정해서는 안 되며, 마지막 신뢰할 수 있는 응답의 나이와 현재 허용되는 저하 작업을 알아야 합니다. 그래야 서비스 복구, 능동적 제한, 권한 변경이 각각 명확한 기록을 가지며, 모두 "캐시가 정상"이라는 네 글자로 접히지 않습니다.

출처

위의 5개 링크는 동일한 커밋의 README,PortalWSClient,PortalWSClientTest,TokenData,PortalHttpClient를 각각 가리킵니다. 이것들은 공개 소스 코드와 모의 테스트 시나리오에 대한 분석을 지지하며, 프로덕션 채택, 테스트 실행, 실제 토큰의 유효성, 철회 우회, 자격 증명 유출 또는 권한 없는 작업을 증명하지 않습니다.