[BE-08] PartnerApiKeyAuthenticationFilter (API Key → 연동 User principal 주입 + 감사 트리거)

작업 내용 (설계 의도)

변경 사항

근거 TDD: ../TDD.md “Sequence Diagram”·ADR-007/008/009. McpTokenAuthenticationFilter 패턴 답습. SecurityConfig 수정은 BE-10 (이 티켓은 필터 클래스만 — Single Writer).

  • infrastructure/security/PartnerApiKeyAuthenticationFilter.kt (OncePerRequestFilter):
    1. Authorization: Bearer partner_<keyId>_<random> 파싱. partner_ prefix 아니면 pass-through.
    2. partnerDomainService.authenticate(keyId, plainKey) → 실패 시 401(무효/REVOKED)·403(SUSPENDED) JSON 응답, 체인 중단.
    3. 성공 시 userDomainService.getRolesForUser(linkedUserId)로 roles 로딩 → UserPrincipal(id=linkedUserId, email, roles) + ROLE_* authorities로 SecurityContext 주입(기존 UserPrincipal 재사용).
    4. partnerApiKey.recordUsage() 반영(lastUsedAt).
    5. filterChain.doFilter 후 응답 status·latency로 PartnerActivityRecorder.record(...) 호출(@Async, BE-04 구현).
  • partner.auth.enabled 플래그는 SecurityConfig 등록 시 게이트(BE-10). 필터 자체는 prefix 미매칭 pass-through라 기존 트래픽 무영향.

롤백: 플래그 OFF(BE-10 등록 게이트) 시 필터 미등록 → 파트너 인증 경로 휴면.

의존

  • BE-01, BE-02, BE-05

다이어그램

처리 흐름

sequenceDiagram
    participant C as Client
    participant F as PartnerApiKeyFilter
    participant P as PartnerDomainService
    participant U as UserDomainService(기존)
    C->>F: Bearer partner_<id>_<rand>
    F->>P: authenticate(keyId, plainKey)
    P-->>F: (partnerId, linkedUserId)
    F->>U: getRolesForUser(linkedUserId)
    F->>F: inject SecurityContext(UserPrincipal)
    F->>F: doFilter → record(@Async)

테스트 케이스

  • 유효 ACTIVE 키 요청 시 연동 User principal(roles 포함)이 SecurityContext에 주입되고 체인이 진행된다.
  • partner_ prefix가 아닌 Authorization은 pass-through한다(JWT 경로 무영향).
  • REVOKED 키 요청은 401 JSON을 반환하고 체인을 중단한다.
  • SUSPENDED 파트너 키 요청은 403을 반환한다.
  • hash 불일치 키는 401을 반환한다.
  • 체인 완료 후 PartnerActivityRecorder.record가 응답 status·latency로 호출된다.
  • 감사 record가 실패해도 요청 응답에는 영향이 없다.