← 개발 로그 목록

website: 학번 로그인 기반 멤버 계정 체계 및 프로젝트 쇼케이스 API 구현

/ 11분 분량 / 개발 로그

멋사 경희대 사이트 프로젝트에서 이번 PR에서는 두 가지 중요한 기능을 구현했습니다. 첫 번째는 부원들이 학번으로 로그인할 수 있도록 멤버 계정 체계를 구축하는 것이고, 두 번째는 부원들의 프로젝트 경험을 공유하기 위한 프로젝트 쇼케이스 API를 추가하는 것입니다. 이 PR은 2026년 7월 20일에 `dev` 브랜치로 병합되었습니다.

요약

이번 PR의 핵심은 다음과 같습니다.

  • 멤버 계정 체계 구축: 기존 운영진 로그인 방식에 더해, 부원들이 학번과 초기 비밀번호(전화번호)로 로그인하고, 첫 로그인 시 비밀번호 변경을 강제하며, 관리자가 비밀번호를 초기화할 수 있는 기능을 구현했습니다. 기존의 로그인, JWT, 쿠키, 비밀번호 처리 로직을 재활용하면서 멤버 전용 테이블을 추가하여 확장했습니다.
  • 프로젝트 쇼케이스 API 구현: 부원들이 참여한 프로젝트 정보를 등록, 조회, 수정, 삭제할 수 있는 API를 개발했습니다. 프로젝트의 상세 정보, 참여 멤버, 이미지 등을 관리할 수 있으며, 공개/비공개 설정 기능도 포함했습니다.
  • 보안 및 계약 강화: 리뷰 과정에서 발견된 여러 보안 취약점(권한 상승, 비밀번호 탈취 경로 등)과 API 계약 불일치 문제를 수정하여 시스템의 안정성과 보안성을 높였습니다.

배경 및 목적

기존에는 사이트에 운영진만 로그인할 수 있어 부원들은 서비스 이용에 제한이 있었습니다. 따라서 부원들이 학번으로 로그인하여 사이트 기능을 활용할 수 있도록 하는 것이 시급했습니다. 또한, 동아리 활동의 결과물인 프로젝트들을 체계적으로 기록하고 공유할 수 있는 기능이 필요했습니다.

이 PR은 다음 이슈들을 해결하기 위해 진행되었습니다:

  • #117: 계정·인증 — 학번 로그인 기반 멤버 계정 체계
  • #119: 프로젝트 쇼케이스 — 목록·상세 API + 계약

구현 내용

1. 멤버 계정 체계 구현 (feat/#117-member-auth)

  • 학번 기반 로그인: 기존 @studentId와 전화번호(초기 비밀번호)로 로그인할 수 있도록 MemberAuthService와 MemberAuthController를 구현했습니다. #97 관리자 인증 파운데이션의 JWT, 쿠키, 잠금 로직을 Member 엔티티에 맞게 오버로드하여 재사용했습니다.
  • 첫 로그인 비밀번호 변경 강제: MemberPasswordGuardFilter를 통해 비밀번호를 변경하지 않은 멤버의 요청을 서버단에서 차단하도록 했습니다. 이는 프론트엔드 안내만으로는 우회될 수 있는 보안 취약점을 보완합니다.
  • 관리자 비밀번호 초기화: 관리자(ADMIN 이상)는 특정 멤버의 비밀번호를 전화번호로 초기화할 수 있는 API(POST /api/admin/members/{id}/password/reset)를 제공합니다.
  • 역할 확장: 기존 SUPER_ADMIN/ADMIN에 MEMBER 역할을 추가하여 총 4단계의 역할 체계를 구성했습니다.
  • 멤버 Refresh Token 별도 관리: 멤버의 Refresh Token은 관리자와 별도의 테이블(member_refresh_tokens)에서 관리하여 ID 충돌 및 기존 관리자 흐름 훼손을 방지했습니다.

2. 프로젝트 쇼케이스 API 구현 (feat/#119-project-showcase)

  • 프로젝트 생성: POST /api/projects 엔드포인트를 통해 프로젝트 정보(제목, 요약, 기술 스택, Github URL 등)와 참여 멤버, 이미지를 등록할 수 있습니다.
    • 참여자 목록에는 프로젝트를 생성하는 멤버 본인이 반드시 포함되어야 하며, 중복된 멤버 ID나 존재하지 않는 멤버 ID는 허용되지 않습니다.
    • 이미지는 최대 한 개만 대표 이미지로 설정 가능합니다.
  • 프로젝트 목록 및 상세 조회:
    • GET /api/projects: 공개된 프로젝트 목록을 조회하며, 숨김 처리된 프로젝트는 제외됩니다. 대표 이미지 URL이 포함됩니다.
    • GET /api/projects/{id}: 특정 프로젝트의 상세 정보를 조회합니다. 이미지와 참여 멤버 정보가 포함됩니다. 숨김 처리된 프로젝트는 404 Not Found를 반환합니다.
  • 프로젝트 수정: PATCH /api/projects/{id} 엔드포인트를 통해 프로젝트 정보, 이미지, 참여 멤버를 수정할 수 있습니다. 프로젝트 수정 권한은 해당 프로젝트의 참여 멤버에게만 주어집니다.
  • 프로젝트 삭제: DELETE /api/projects/{id} 엔드포인트를 통해 프로젝트를 삭제할 수 있습니다. 삭제 권한 또한 해당 프로젝트의 참여 멤버에게만 주어집니다.
  • 프로젝트 숨김/복원: PATCH /api/admin/projects/{id}/hidden 엔드포인트를 통해 관리자는 프로젝트를 숨김(public 목록에서 제외) 처리하거나 복원할 수 있습니다.

3. 보안 및 계약 강화 (PR 리뷰 대응)

feat/#117-member-auth PR 리뷰 및 feat/#119-project-showcase PR 리뷰 과정에서 발견된 문제점들을 해결했습니다.

  • 권한 상승 방지: PostController의 /api/admin/posts/** 경로에 MEMBER 역할 접근이 가능해지면서 발생할 수 있는 권한 상승 취약점을 @PreAuthorize("hasAnyRole('ADMIN','SUPER_ADMIN')") 추가로 해결했습니다.
  • API 계약 불일치 해결: changePassword/resetPasswordByAdmin 등에서 ResponseStatusException을 직접 던져 Spring 기본 에러 바디가 나가던 문제를 MemberNotFoundException 신설 및 GlobalExceptionHandler 등록을 통해 통일된 응답 형식({success, message, code})으로 수정했습니다.
  • 비밀번호 변경 시 현재 비밀번호 확인: PATCH /api/member/auth/password 엔드포인트에 currentPassword 필드를 필수화하고 검증 로직을 추가하여, 로그인된 기기 탈취 시 계정 영구 탈취 가능성을 차단했습니다.
  • MemberPasswordGuardFilter 스코프 축소: 멤버 인증 관련 필터의 스코프를 /api/member/ 네임스페이스로 좁히고, 인증 모듈 자체(api/member/auth/**)는 예외 처리하여 공개 API 접근 제한 문제를 해결했습니다. 또한, 이 가드를 모든 쓰기 메서드(POST/PUT/PATCH/DELETE)에 적용되도록 일반화하여 projects API의 mustChangePassword 멤버 접근 시 발생할 수 있는 문제를 미리 방지했습니다.
  • 오류 응답 형식 통일: 프로젝트 API에서 발생하는 다양한 비즈니스 규칙 위반(참여자 중복, 대표 이미지 미지정 등)에 대해 ResponseStatusException 대신 전용 예외와 GlobalExceptionHandler를 사용하여 일관된 에러 응답({success, message, code})을 반환하도록 수정했습니다.

기술적 의사결정

  • 멤버 인증 로직 재활용: 기존 관리자 인증 모듈의 JWT, 쿠키, 비밀번호 처리 로직을 그대로 멤버 인증에 재활용했습니다. 이는 코드 중복을 최소화하고 일관된 보안 정책을 유지하기 위함입니다. (#117 설명에서 언급)
  • MemberPasswordGuardFilter 일반화: /api/member/ 경로 기반으로 멤버의 mustChangePassword 상태를 체크하던 필터를, POST/PUT/PATCH/DELETE와 같은 쓰기 메서드에 기반하여 일반화했습니다. 이는 새로운 API 추가 시에도 일관된 보안 정책을 적용하기 용이하게 하며, projects API와 같이 /api/member/ 네임스페이스 밖에 있더라도 멤버 인증 관련 쓰기 작업에 mustChangePassword 상태를 강제할 수 있게 합니다. (fix/#117-member-auth 커밋 메시지 참고)
  • 상태공간 트리 QA: 복잡한 인증 로직 및 API 엔드포인트별 접근 권한 검증을 위해, Actor × TokenState × MustChangePassword × Endpoint와 같이 가능한 모든 조합을 트리 형태로 구성하고 백트래킹 방식으로 테스트 케이스를 도출했습니다. 이를 통해 기존 테스트에서 놓쳤던 4가지 멤버 인증 관련 빈틈과 2가지 프로젝트 API 관련 빈틈을 발견하고 테스트 코드를 추가했습니다. (test/#117-state-space-qa 커밋 메시지 참고)

배운 점 및 개선점

배운 점

  • 상태공간 트리 QA의 유용성: 복잡한 로직의 경계 케이스를 체계적으로 탐색하고 테스트 커버리지를 확보하는 데 상태공간 트리 QA가 매우 효과적임을 경험했습니다. 특히, 인증 및 권한 관련 복잡한 로직에서 발생하는 미묘한 차이를 발견하는 데 큰 도움이 되었습니다.
  • API 설계의 일관성: 다양한 도메인에서 일관된 에러 응답 형식({success, message, code})을 유지하는 것이 API 사용성과 유지보수성에 중요하다는 것을 알게 되었습니다.
  • 필터 스코프의 중요성: MemberPasswordGuardFilter를 경로 기반에서 메서드 기반으로 변경하면서, API 설계 변경 시 보안 필터의 적용 범위와 정책을 재검토해야 함을 배웠습니다.

개선점

  • 멤버 전용 API 추가: 현재 MemberPasswordGuardFilter는 POST/PUT/PATCH/DELETE 메서드를 기반으로 동작하지만, 실제로 멤버 전용 API(예: 글쓰기, 프로필 편집)가 더 많이 추가될 예정입니다. 이러한 API들이 mustChangePassword 상태일 때 올바르게 차단되는지 지속적으로 검증하고, 필요하다면 MemberPasswordGuardFilter의 로직을 더 정교하게 다듬거나 해당 API들에 대한 별도의 테스트를 강화해야 합니다.
  • JWT 만료 시점 관리: 현재 Refresh Token은 7일, Access Token은 1시간으로 설정되어 있습니다. 운영 환경에서는 더 세밀한 만료 시점 관리 및 동시 접속자 수 제한 등 추가적인 보안 정책 고려가 필요할 수 있습니다.
  • JwtProvider의 iat 중복 문제: 같은 유저를 같은 밀리초에 두 번 로그인시키면 Refresh Token이 완전히 동일해져 DB Unique 제약과 충돌하는 문제가 있습니다. 이는 실제 운영 환경에서는 발생하기 어렵지만, 자동화 스크립트 등으로 재현될 경우 JwtProvider에 jti(random claim) 추가를 고려해볼 수 있습니다.