📌 Description
QA 시나리오 2 진행 중 발견: 관리자가 사용자에게 역할을 잘못 부여했을 때 되돌릴 방법이 없다 —
UserRoleCommandService/AdminUserController/프론트 어디에도 revoke 기능이 없다(POST /admin/users/{userId}/roles만 존재).
또한 실제로 역할을 부여/회수해도 로그아웃하기 전까지는 반영되지 않는다. JwtAuthenticationFilter가
로그인 시점에 발급된 JWT의 roles claim만으로 hasRole(...) 인가를 판단하기
때문에(JwtProvider.generateToken()이 role을 토큰에 baked-in), DB의 user_roles가 바뀌어도 이미 발급된
토큰은 그대로다. GET /auth/me는 매번 DB를 새로 읽어 화면엔 바뀐 것처럼 보이지만, 실제 /admin/** 등 인가
판단에는 반영되지 않는 불일치가 있다.
이 문제는 권한 부여보다 회수 쪽에서 더 심각하다 — 관리자가 잘못 준 ADMIN 권한을 회수해도, 대상자가
로그아웃하지 않는 한 계속 관리자 기능을 쓸 수 있는 보안 문제가 된다. 그래서 이번 이슈는 역할 회수 API와 즉시
반영 인프라를 함께 다룬다.
DELETE /admin/users/{userId}/roles/{roleCode}
- ADMIN 권한 필요
- 부여되지 않은 역할을 회수 시도하면 400/404 (에러코드 신규 정의 필요)
즉시 반영 방식
- JwtAuthenticationFilter가 JWT의 roles claim 대신, 매 요청마다 UserRoleRepository로 최신 role을 조회해
GrantedAuthority를 구성한다. JWT에는 userId/email(신원)만 남긴다.
- 매 요청 DB 조회 부하를 피하기 위해 Redis에 user:{userId}:roles를 짧은 TTL로 캐싱한다
(TokenBlacklistService가 이미 쓰는 Redis 재사용, 신규 인프라 불필요).
- assignRole() / 신규 revokeRole()이 DB 저장 직후 해당 사용자의 캐시를 즉시 무효화해, 로그아웃 없이도 다음
요청부터 바로 반영되게 한다.
✅ To-do
- [ ] DELETE /admin/users/{userId}/roles/{roleCode} API 추가 (UserRoleCommandService.revokeRole())
- [ ] 미부여 역할 회수 시 에러코드 신규 정의
- [ ] JwtAuthenticationFilter를 DB 조회 기반으로 변경 (JWT는 신원만 담당)
- [ ] Redis 역할 캐시 + TTL 적용
- [ ] assignRole/revokeRole에서 캐시 무효화 로직 추가
- [ ] 프론트 /admin/users에 역할 회수 버튼 추가
- [ ] 단위/컨트롤러 테스트: 회수 성공, 미부여 역할 회수 실패, 비ADMIN 403, 부여/회수 직후 재로그인 없이 권한
반영되는지 통합 테스트
✅ 완료 기준
- [ ] 관리자가 부여한 역할을 회수할 수 있다.
- [ ] 부여/회수 직후, 대상 사용자가 로그아웃하지 않아도 다음 요청부터 실제 권한이 반영된다.
- [ ] 인증된 요청마다 DB 부하가 과도하게 늘지 않는다 (Redis 캐시 히트로 대부분 처리).
- [ ] ADMIN이 아닌 사용자는 회수 API를 호출할 수 없다 (403).
📒 기타
- QA 시나리오 2에서 실측: 승격 후 /auth/me는 즉시 새 role을 보여주지만, 재로그인 전까지 실제 /admin/**
접근은 여전히 403 (요청을 처리하지 못했습니다 — Spring Security 기본 403 응답이 앱 표준 에러 포맷이 아니라
프론트에서 메시지를 못 뽑아내는 부수 이슈도 있음, 필요시 별도 이슈로 분리)
- 상세 설계: docs/design/{github아이디}-#{이슈번호}-role-revoke-live-permission.md (PR과 함께 작성 예정)
📌 Description
QA 시나리오 2 진행 중 발견: 관리자가 사용자에게 역할을 잘못 부여했을 때 되돌릴 방법이 없다 —
UserRoleCommandService/AdminUserController/프론트 어디에도 revoke 기능이 없다(POST /admin/users/{userId}/roles만 존재).또한 실제로 역할을 부여/회수해도 로그아웃하기 전까지는 반영되지 않는다.
JwtAuthenticationFilter가로그인 시점에 발급된 JWT의
rolesclaim만으로hasRole(...)인가를 판단하기때문에(
JwtProvider.generateToken()이 role을 토큰에 baked-in), DB의user_roles가 바뀌어도 이미 발급된토큰은 그대로다.
GET /auth/me는 매번 DB를 새로 읽어 화면엔 바뀐 것처럼 보이지만, 실제/admin/**등 인가판단에는 반영되지 않는 불일치가 있다.
이 문제는 권한 부여보다 회수 쪽에서 더 심각하다 — 관리자가 잘못 준 ADMIN 권한을 회수해도, 대상자가
로그아웃하지 않는 한 계속 관리자 기능을 쓸 수 있는 보안 문제가 된다. 그래서 이번 이슈는 역할 회수 API와 즉시
반영 인프라를 함께 다룬다.