Repository navigation
Conversation
- AccessDeniedException이 RuntimeException 핸들러에 잡혀 HTTP 200으로 내려가던 문제 수정 - AccessDeniedHandler가 401 대신 403(FORBIDDEN)을 반환하도록 변경 - INACTIVE_MEMBER(401), FORBIDDEN(403) 상태 추가
- MemberAuthority(MEMBER/MANAGER/OWNER) 추가, role은 YB/OB만 담당 - 로그인 시 기수 기반 role 재계산이 어드민 권한을 덮어쓰던 문제 해결 - 로그인/토큰 갱신 응답에 authority 필드 추가 - 기동 시 기존 role=ADMIN 회원을 MANAGER로 이전, admin.owner-ids로 최초 OWNER 지정
- RoleHierarchy(OWNER > MANAGER > MEMBER) 적용 - /admin/owner/** OWNER, /admin/** MANAGER 이상 URL 인가 + 컨트롤러 @PreAuthorize 이중 적용 - 마이페이지/채팅/로그아웃 API는 인증 필수로 변경 - 승인되지 않았거나 탈퇴한 회원의 토큰은 401(INACTIVE_MEMBER) 처리 - CORS 허용 origin을 cors.allowed-origins 설정으로 분리하고 PATCH/OPTIONS 허용 - GET /admin/members 회원 목록 조회 API 추가
- PATCH /admin/owner/members/{userId}/authority
- 자기 자신 변경 금지, 승인된 회원만 권한 부여 가능
- OWNER 행 비관적 락으로 동시 강등 시에도 마지막 OWNER 보호
- 권한별(비로그인/MEMBER/MANAGER/OWNER) API 접근 검증 - 권한 변경 규칙, 탈퇴 회원 토큰, 레거시 ADMIN 이전 검증 - 테스트 DB를 H2(PostgreSQL 모드)로 구성
psm1st
force-pushed
the
feature/#58-admin-authorization
branch
from
October 12, 2026 06:33
7fba847 to
04c92f8
Compare
hyunbin7664
reviewed
Oct 15, 2026
Comment on lines
+47
to
+59
| if (ownerIds.isEmpty()) { | ||
| return; | ||
| } | ||
| for (Member member : memberRepository.findAllByIdIn(ownerIds)) { | ||
| if (member.getStatus() != MemberStatus.APPROVED) { | ||
| log.warn("OWNER 지정 실패 (승인되지 않은 회원): id={}", member.getId()); | ||
| continue; | ||
| } | ||
| if (member.getAuthority() != MemberAuthority.OWNER) { | ||
| member.updateAuthority(MemberAuthority.OWNER); | ||
| log.info("OWNER 권한 부여: id={}", member.getId()); | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
서버가 켜질 때마다 admin.owner-ids에 있는 회원을 다시 OWNER로 올리고 있어서 API로 OWNER를 강등해도 재배포하면 다시 OWNER가 될 것 같아요
OWNER가 한 명도 없을 때만 실행하면 어떨까요?
if (ownerIds.isEmpty() || !memberRepository.findAllByAuthority(MemberAuthority.OWNER).isEmpty()) {
return;
}이렇게 하면 마지막 OWNER가 탈퇴해도 재시작만 하면 다시 복구될 것 같습니다!
| .body(BaseResponse.error(BaseExceptionStatus.FORBIDDEN)); | ||
| } | ||
|
|
||
| @ExceptionHandler(RuntimeException.class) |
Member
There was a problem hiding this comment.
지금은 잘못된 요청도 이 핸들러에 잡혀서 400이 아닌 500으로 응답되는 것 같습니다
GET /admin/members?status=FOOPATCH .../authority에{"authority": "ADMIN"}
어드민 FE 연동을 시작하면 자주 생길 것 같아서 아래처럼 400으로 따로 처리하면 좋을 것 같아요!
@ExceptionHandler({MethodArgumentTypeMismatchException.class, HttpMessageNotReadableException.class})
public ResponseEntity<BaseResponse<BaseException>> handleBadRequest(Exception ex) {
return ResponseEntity.badRequest()
.body(BaseResponse.error(BaseExceptionStatus.BAD_REQUEST));
}This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 작업 개요
관련 이슈
작업 배경 및 목적
/admin/**가 로그인 여부만 확인하고 있어, 일반 학회원(YB/OB)도 학회 소개·프로젝트·후기를 등록/수정/삭제할 수 있었습니다.MemberRole {YB, OB, ADMIN}에 학회원 구분과 어드민 권한이 섞여 있어, 로그인 시 기수 기반 role 재계산이ADMIN을 덮어썼습니다. 기수가 있는 운영진은 로그인할 때마다 관리자 권한을 잃었습니다.AccessDeniedException)가RuntimeException핸들러에 잡혀 HTTP 200 + code 500으로 응답돼, FE가 권한 오류를 구분할 수 없었습니다.🛠️ 주요 변경 사항
1. 학회원 구분과 어드민 권한 분리
MemberAuthority { MEMBER, MANAGER, OWNER }추가,Member.authority컬럼(member_authority) 추가role은 YB/OB만 담당하며 기수 기준 자동 판별을 유지합니다.authority는 OWNER만 변경할 수 있습니다.authority필드를 추가했습니다.2. 역할 기반 인가
RoleHierarchy:OWNER > MANAGER > MEMBER/admin/owner/**는 OWNER,/admin/**는 MANAGER 이상AdminController에@PreAuthorize("hasRole('MANAGER')"),OwnerController에@PreAuthorize("hasRole('OWNER')")/api/mypage/**,/api/chat/**,/api/auth/logout은 인증 필수로 변경했습니다. 기존에는 컨트롤러에서 400을 반환했고, 이제 401을 반환합니다.INACTIVE_MEMBER)로 처리합니다.3. 신규 API
/admin/members?status=/admin/owner/members/{userId}/authority권한 변경 규칙
4. 에러 응답 정리
FORBIDDEN, 미인증은 401입니다.5. 기존 데이터 이전 (
AdminAuthorityInitializer)member_role = 'ADMIN'인 회원을authority = MANAGER로 이전하고, role은 YB/OB로 재계산합니다.admin.owner-ids에 지정한 로그인 ID를 OWNER로 승격합니다. 최초 OWNER를 만들기 위한 설정입니다.6. 기타
WebConfig)을SecurityConfig로 통합했습니다. 허용 origin은cors.allowed-origins설정으로 분리했고,PATCH/OPTIONS를 허용했습니다.docs/admin-authorization.md✅ 완료 조건
/admin/**유지🧪 테스트 및 검증 결과
ADMIN데이터 이전테스트 환경
테스트 증빙
로컬 서버 기동 로그 (레거시 ADMIN 회원이 있는 DB로 기동):
로컬 API 호출 결과:
미검증 항목
🔍 리뷰 요청 사항
role(YB/OB)과authority(MEMBER/MANAGER/OWNER)를 분리한 방향이 적절한지 봐 주세요.MemberRole.ADMIN은 기존 데이터를 읽기 위해@Deprecated로만 남겼습니다./api/**인증 범위: 인증 필수로 바꾼 경로(mypage,chat,logout)가 맞는지 확인 부탁드립니다. 멘토링 상세/신청은 기존처럼 컨트롤러에서 검사합니다.RuntimeException응답 변경: 200에서 500으로 바뀐 것이 사이트 FE에 영향이 없는지 확인이 필요합니다.member.member_authority컬럼 추가 (ddl-auto=update로 자동 생성)member_role = 'ADMIN'회원을member_authority = 'MANAGER'로 이전하고,member_role은 YB/OB로 재계산admin.owner-ids=<OWNER 로그인 ID 목록>: 최초 OWNER 지정용으로 배포 시 필요합니다.cors.allowed-origins=<사이트 FE>,<어드민 FE>: 선택. 미설정 시 기존처럼*을 허용합니다.role은 YB/OB만 내려가고,authority가 추가됩니다. 어드민 FE는authority !== 'MEMBER'로 접근을 판별합니다./api/mypage/**,/api/chat/**,/api/auth/logout비로그인 시 400에서 401로 변경GET /admin/members,PATCH /admin/owner/members/{userId}/authorityspring-security-test,h2(testRuntimeOnly)를 추가했습니다.☑️ PR 체크리스트
🤖 Generated with Claude Code