[LIVErary] Spring Boot context-path 설정 시 403 에러 해결 (경로 매핑 중복 문제)
application.yml에 server.servlet.context-path를 설정했을 때, Spring Security와 Controller에서 403 Forbidden 에러가 발생하는 원인과 해결 방법을 정리했습니다.
API 버전을 관리하기 위해 application.yml에 context-path를 설정했다. 그런데 분명히 권한을 다 열어주었는데도(permitAll), API 요청 시 403 Forbidden 에러가 계속 발생했다.
알고 보니 Spring이 URL을 해석하는 방식을 오해해서 발생한 문제였다. context-path의 동작 원리와 해결 과정을 정리한다.
문제 상황
application.yml에 API의 기본 경로를 /api/v1으로 설정해 두었다.
1
2
3
4
5
# application.yml
server:
servlet:
context-path: /api/v1 # 모든 요청 앞에 이 경로가 붙음
그리고 SecurityConfig와 Controller에서도 명확하게 경로를 지정해 주었다. (라고 생각했다.)
1
2
3
4
5
6
// 실수한 코드 (SecurityConfig)
.requestMatchers("/api/v1/room/**").permitAll() // 전체 경로를 다 적음
// 실수한 코드 (Controller)
@RequestMapping("/api/v1/room") // 전체 경로를 다 적음
하지만 Postman으로 http://localhost:8080/api/v1/room을 호출하면 403 Forbidden이 반환되었다.
원인 분석
Spring Boot에서 context-path를 설정하면, 서버는 들어오는 모든 요청의 맨 앞부분(context-path)을 자동으로 잘라내고(Strip), 그 뒷부분(Path)만 가지고 내부 로직을 수행한다.
Spring의 시선
- 실제 요청 URL:
http://localhost:8080/api/v1/room - Spring이 잘라내는 부분 (Context Path):
/api/v1 - Security & Controller가 보는 주소:
/room(앞부분이 이미 제거됨!)
그런데 내 코드는 /api/v1/room이 들어오는지 검사하고 있었다. Spring 입장에서는 /room만 들어왔는데, 설정 파일에서는 /api/v1/room을 찾으라고 하니 매칭되는 규칙이 없어 기본 설정인 ‘거절(403)’ 처리를 해버린 것이다.
해결 방법
SecurityConfig와 Controller에서 중복된 경로(/api/v1)를 제거하고 상대 경로만 남겨야 한다.
SecurityConfig.java 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// [수정] 앞에 /api/v1을 제거하고 뒷부분만 명시
.requestMatchers("/room/**").permitAll()
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
Controller 수정
Controller도 마찬가지다. context-path가 이미 앞단을 처리해 주므로, 여기서는 /room만 적어야 한다.
1
2
3
4
5
6
7
8
// [수정] /api/v1/room (X) -> /room (O)
@RequestMapping("/room")
@RestController
@RequiredArgsConstructor
public class RoomController {
// ...
}
최종 확인
코드를 수정하고 서버를 재시작한다.
- Postman 요청:
http://localhost:8080/api/v1/room- (주의: 클라이언트(Postman)는 서버 밖에서 요청하므로 여전히 전체 주소를 써야 한다!)
- 결과:
200 OK