← 문제로

48. 장수명 연결의 인증·권한 만료 재검증 부재(만료 후 권한 유지) · C#

난이도 중
내 리뷰 · C#
해설 · C#

해설 — 장수명 연결의 인증·권한 만료 재검증 부재(만료 후 권한 유지) · C#

난이도: 중

요약

접속 시 인증에서 Authenticated=true 와 TokenExpiresAtUtc 를 저장하지만(A), 이후 보호된 요청 처리(B)는 오직 Authenticated 불리언만 검사하고 만료 시각과 현재 시각을 대조하지 않는다. TCP 연결이 만료 시각을 지나 계속 살아 있으면, 만료된 토큰의 권한이 세션 수명 내내 유효하게 남는다.

문제점

만료 시각을 사용 시점에 재검증하지 않음(check-at-connect only) — 시간제 권한 우회

  • 증상: 프리미엄/구독/시간제 접근이 만료된 뒤에도 같은 연결에서 계속 프리미엄 구매가 통과한다. 밴이 시작되거나 권한이 강등돼도 이미 열린 연결은 옛 권한을 유지한다.
  • 재현 조건:
    1. 클라이언트가 ExpiresAtUtc = T+10분 인 토큰으로 접속. (A)에서 Authenticated=true.
    2. 연결을 끊지 않고 유지(장수명 TCP).
    3. T+30분에 OnPremiumPurchase 호출. (B)는 Authenticated==true 만 보고 통과 → 만료 20분 지난 권한으로 구매 성공.
  • 근본 원인: 인증은 "이벤트(접속 순간)"가 아니라 "시간에 따라 변하는 상태(현재 유효한가)"인데, 코드는 접속 순간의 스냅샷 불리언만 본다. TokenExpiresAtUtc 를 저장해 놓고도 어디서도 DateTime.UtcNow 와 비교하지 않는다.

수정안

보호된 동작마다 만료를 재검증한다.

private bool IsCurrentlyAuthorized()
    => _session.Authenticated && DateTime.UtcNow < _session.TokenExpiresAtUtc;

public void OnPremiumPurchase(int itemId)
{
    if (!IsCurrentlyAuthorized())
    {
        _session.Authenticated = false;       // 만료 확정
        Reject("token expired, re-authenticate");
        return;
    }
    PremiumStore.Purchase(_session.AccountId, itemId);
}

더 견고하게는 만료를 능동적으로 처리한다. 세션에 만료 타이머를 걸어 만료 시 권한을 내리고 재인증을 요구하며, 즉시 무효화가 필요한 밴/강등은 별도 통지(펍섭)로 세션 상태를 갱신한다. 토큰이 짧게 만료되고 슬라이딩 갱신이 필요하면, 클라이언트가 주기적으로 갱신 토큰을 제시하고 서버가 새 만료 시각으로 교체한다.

더 나은 설계

  • 권한은 매 사용 시점에 평가: "로그인했는가"가 아니라 "지금 이 동작을 할 권한이 있는가"를 검사한다. 만료·밴·강등처럼 시간에 따라 변하는 권한은 특히 그렇다.
  • 짧은 만료 + 갱신: 장수명 연결일수록 토큰 만료를 짧게 잡고 갱신을 요구하면 탈취·강등 지연 노출 창이 준다.
  • 즉시 무효화 경로: 밴/결제 취소는 만료를 기다리지 않고 반영돼야 하므로, 세션 저장소를 구독해 실시간으로 권한을 회수하는 경로를 둔다.
  • 트레이드오프: 매 요청 시각 비교는 비용이 무시할 만하다. 실시간 무효화(펍섭)는 인프라 비용이 있으니 민감한 권한(결제·GM)에만 적용하는 절충이 흔하다.

면접 포인트

  • "접속 시 한 번 인증하고 이후 불리언만 본다"는 장수명 연결에서 시간제 권한을 새게 만드는 전형적 결함이다. 권한 결정은 상태가 아니라 "현재 시각 기준 재평가"여야 한다.
  • 인증(누구인가)과 인가(지금 무엇을 할 수 있는가)를 분리해서 생각하면, 인가는 매 요청 평가 대상임이 분명해진다.