오류 로그 (요약)
2025-05-21T12:44:14.229+09:00 INFO ... SseService : SSE 연결 완료: userId=1
...
AccessDeniedException: Access Denied
문제 개요
- SseAuthenticationFilter에서 JWT 인증은 성공하고, SecurityContext에 사용자 ID도 정상적으로 설정됨 (@AuthenticationPrincipal도 잘 동작).
- SseService.subscribe(userId) 실행도 문제 없음. 하지만 emitter.send(...)로 응답을 먼저 보낸 뒤, Spring Security의 AuthorizationFilter가 뒤늦게 권한 검사를 수행 → 실패 → 예외 발생.
- 이때는 이미 응답이 전송된 상태라, ExceptionTranslationFilter가 예외를 정상적으로 처리할 수 없음 → 로그만 출력됨.
원인 분석
왜 이런 문제가 발생했는가?
Spring Security는 인증(Authentication)과 권한 검사(Authorization)를 분리해서 수행합니다.
- SseAuthenticationFilter에서는 다음과 같이 설정→ 즉, 권한(authority)이 없는 인증 객체를 설정
- new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList())
- /sse/subscribe 경로를 .permitAll()로 설정해도, Spring Security 내부 처리 과정에서 getAuthorities() 호출이 발생하면 AccessDeniedException이 발생할 수 있습니다.
👉 직접적으로 getAuthorities()를 호출하지 않았더라도, SSE 통신 도중 Spring Security의 필터나 핸들러 내부에서 권한 확인 로직이 실행된 것으로 추정됩니다.
하지만 아래와 같이 permitAll()로 설정했는데도 왜 권한을 요구할까요?
.requestMatchers("/sse/subscribe").permitAll()
이건 "인증이 없을 수도 있는 요청은 허용한다"는 의미지, "권한이 없는 인증된 사용자의 요청도 허용"이라는 뜻은 아닙니다.
즉, 인증 객체가 존재하고 authorities가 비어 있다면, Spring Security는 그걸 "인증은 됐지만 권한 없음"으로 간주해서 AccessDeniedException을 던질 수 있습니다.
해결 방법
권한이 포함된 Authentication 객체로 설정
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userId,
null,
List.of(new SimpleGrantedAuthority("ROLE_USER")) // ← 여기에 권한 부여
);
→ 현재 저희 서비스 기획상 사용자는 일반 유저 권한만을 가지도록 되어 있어, 우선 해당 전제로 임시 "ROLE_USER"로 구현해두었습니다. 추후에 다른 권한 유형이 추가될 경우, 이를 유연하게 처리할 수 있도록 동적 처리 방식으로 확장할 계획입니다.


끝.
'카카오테크 부트캠프' 카테고리의 다른 글
| SSE 연결로 인한 HikariCP 커넥션 미반납 문제 (0) | 2025.06.14 |
|---|---|
| Java의 record 타입 — DTO에 적합한 이유와 실제 적용기 (1) | 2025.06.10 |
| boolean 필드가 JSON에서 read로 직렬화되는 이유 (0) | 2025.05.25 |
| 채팅 도메인 - SSE 도입 테크스펙 (0) | 2025.05.16 |
| Spring Security에서 AccessTokenExpiredException(커스텀 예외)이 무시되는 이유와 해결기 (0) | 2025.05.11 |