Skip to content

[Feat/#311] AI 서버용 읽기 전용 내부 API 추가 - #312

Open
overhyeon wants to merge 3 commits into
mainfrom
feat/#311-ai-internal-api
Open

overhyeon wants to merge 3 commits into
mainfrom
feat/#311-ai-internal-api

Conversation

@overhyeon

Copy link
Copy Markdown
Contributor

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

챗봇이 공지·행사·아카이빙·열린피드백을 미리 모아 검색 인덱스를 만들어야 하는데, 이 일을 하는 AI 서버 동기화 작업(sync.py)은 학생 질문 없이 혼자 돈다. 넘겨받을 학생 JWT가 없어서 지금 백엔드 API로는 읽어 올 방법이 없다.
(질문할 때 조회하는 재고·내 대여는 학생 JWT 전달로 해결했다 — #310)

❓ 왜 해결해야 하나요?

챗봇은 지금 직접 쓴 시드 문서로만 답한다. 학생회가 실제로 올린 공지·행사로 답해야 슬랑제 시연과 실제 운영이 의미가 있다.

⭐ 어떻게 해결했나요?

1. 새 모듈 api/ai-api (/v1/ai/**) — 컨벤션의 클라이언트 기준(architecture.md 2-2절)에 따라 AI 서버를 세 번째 클라이언트로 뒀다. GET만 있다.

경로 재사용한 서비스 나오는 것
GET /v1/ai/notices NoticeService.getNotices (끝까지 넘김) 삭제 안 된 공지
GET /v1/ai/events EventService.getPublishedEvents → getPublishedByIds 게시된 행사 (소개글·장소·기간)
GET /v1/ai/archives ArchiveService.getArchives(null, null) → getArchive 학생 목록과 같은 아카이빙
GET /v1/ai/feedbacks FeedbackService.search + PageResult.fetchAll 답변 공개된 회차의 답변된 질문
  • 학생 화면이 쓰는 조회 서비스를 그대로 써서 노출 규칙이 학생 화면과 같다
  • 양이 적어(각 수십~수백 건) 페이지 없이 전체를 본문까지 한 번에 준다
  • 작성자 정보(createdBy, answeredBy), 이미지·첨부는 내보내지 않는다. 특히 열린피드백 질문자

2. /v1/ai/** 전용 보안 체인 (gateway:auth) — 기존 SecurityConfig는 건드리지 않고 @Order(1) 체인을 옆에 뒀다.

  • InternalTokenAuthFilter: X-Internal-Token을 설정값과 MessageDigest.isEqual로 비교. 맞으면 AI_SERVER 권한으로 인증
  • AI_SERVER는 Role enum에 넣지 않았다(JWT·PrincipalProvider를 건드리지 않으려고)
  • 설정값이 비어 있으면 전부 401 (fail closed). CORS는 열지 않는다
  • 필터는 빈으로 등록하지 않는다(JwtAuthFilter와 같은 이유 — 빈이면 모든 요청에 걸린다)

3. 토큰: 새 비밀값 없이 기존 STREAM_AI_INTERNAL_TOKEN을 재사용한다(ai-server.internal-token).

🧩 이 PR의 한계 & 트레이드오프

  • 토큰 하나를 양방향에 쓴다. 새면 양쪽이 다 열리지만, 이 API로 나가는 건 학생이 로그인하면 보는 공개 글뿐이라 피해가 작다. 민감한 데이터를 내보내게 되면 그때 나눈다
  • /v1/ai/**는 프록시에서 막지 않아 밖에서 주소는 닿는다. 토큰 하나가 방어선이다. 슬랑제 이후 프록시 내부망 제한을 검토한다
  • 아카이빙은 본문을 한 건씩 상세로 읽는다(N번 조회). 학기에 수십 건이라 그대로 뒀다
  • 페이지 없이 전체를 준다. 500건을 넘으면 페이지를 붙인다

⛓️ 기존 기능에 미치는 영향

  • 기존 학생·운영진 보안 체인과 API는 그대로다. /v1/ai/**만 새 체인이 맡는다
  • 도메인 서비스는 바꾸지 않았다(호출만 한다)
  • 새 환경변수 없음 (STREAM_AI_INTERNAL_TOKEN 재사용)

🔀 Edge Case & 실패 시나리오

  • 토큰 없음·틀림 → 401 (CommonErrorCode.UNAUTHORIZED, 기존 EntryPoint)
  • 학생 JWT로 /v1/ai/** → 401 (새 체인은 JWT 필터가 없다)
  • 내부 토큰으로 /v1/app/** → 기존 체인이라 401
  • STREAM_AI_INTERNAL_TOKEN 미설정 → /v1/ai/** 전부 401

📋 검토한 대안과 선택 이유

  • 챗봇 전용 학생 JWT 발급: 학생 권한(쓰기 포함)이 통째로 들어 있고, 새면 막는 법이 JWT 비밀키 교체(전원 로그아웃)뿐
  • 내부 토큰이면 기존 /v1/app/** GET 허용: 컨트롤러는 안 늘지만 AppApiUser를 기대하는 엔드포인트가 깨지고 role 모델에 예외가 생긴다
  • app-api 안에 패키지로: 경로가 학생 JWT 구역(/v1/app)이 되어 보안 규칙이 헷갈린다
  • 설계안: billilge/stream-ai docs/plans/ai-internal-api-proposal.md (브랜치 docs/#71-agent-plan)

💬 리뷰 포인트

  • [r] AiServerSecurityConfig의 체인 순서(@Order(1) + securityMatcher)가 기존 체인과 겹치지 않는지
  • [c] ai-api 모듈 신설 / 토큰 재사용 결정이 괜찮은지
  • [c] 응답 필드가 충분한지 (승빈 sync.py가 이 모양으로 작업 시작)

확인한 것: 전체 compileJava·compileTestJava, ModularityTests, DomainImplAccessTests 통과
확인 못 한 것: 로컬에서 서버를 띄운 실제 호출(로컬 DB 없음). dev 배포 후 아래로 확인한다

curl -s -o /dev/null -w "%{http_code}\n" https://<dev 백엔드>/v1/ai/notices                                  # 401
curl -s -H "X-Internal-Token: <값>" https://<dev 백엔드>/v1/ai/notices | head -c 300                           # 200 + 공지
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer <학생 JWT>" https://<dev 백엔드>/v1/ai/notices # 401

@coderabbitai

coderabbitai Bot commented Nov 8, 2026

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI 서버용 읽기 전용 내부 API (공지·행사·아카이빙·열린피드백)

1 participant