프로젝트를 진행하면서 본격적으로 프런트엔드와 협업을 시작하기 전에 ngrok을 이용해 외부와의 통신이 원활하게 연결되는지 테스트했습니다. 그 과정에서 겪은 문제를 기록해보려 합니다. 오류가 있다면 피드백 부탁드립니다!😂
개발환경
- java17
- spring boot 3.2.0
- spring security 6.2.1
- Gradle 8.5
문제
프론트엔드 측에서 요청을 보내자 403(Forbidden) 상태코드가 반환되었습니다. 접근 권한 문제인 것 같은데요, 프런트엔드 팀원에게 물어보니 응답 헤더에 "Access-Control-Allow-Origin"이 누락되어 pre-flight 요청이 실패되는 것 같다는 답변을 받았습니다. (pre-flight란? https://velog.io/@cloudlee711/CORS-%EC%99%80-preflight )
자바는 필터(Filter)와 필터 체인(FilterChain)을 제공해서 엔드포인트로 요청이 도달하기 전에 다양한 기능을 처리할 수 있도록 도와줍니다. 저는 그 많은 기능 중 인증(Authentication)과 인가(Authorization) 부분을 Spring Security의 SecurityFilterChain으로 구현했습니다.
SecurityFilterChain을 적용하면 HttpSecurity 클래스를 사용할 수 있습니다. HttpSecurity를 사용하면 특정 URL 패턴, HTTP 메서드 등에 따른 보안 규칙을 빌더 형태로 적용할 수 있습니다. (최신 버전에서는 세부 내용을 람다로 적용하셔야 합니다.)
@Configuration
@EnableWebSecurity(debug = true) // filter 호출 순서 반환
@Slf4j
public class SecurityConfig {
...
...
...
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers((headers) -> headers.frameOptions(Customizer.withDefaults()))
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptionHandling) ->
exceptionHandling.authenticationEntryPoint(new MemberAuthenticationEntryPoint())
.accessDeniedHandler(new MemberAccessDeniedHandler()))
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/health", "member/signup", "auth/login", "/login/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
,,,
,,,
,,,
.requestMatchers("/mentoring/create").hasRole("MENTOR")
.anyRequest().authenticated()
)
.with(new CustomFilterConfigurer(), Customizer.withDefaults())
.cors((cors) -> cors
.configurationSource(corsConfigurationSource()))
.httpBasic(AbstractHttpConfigurer::disable)
.exceptionHandling(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
return http.build();
}
...
...
...
}
위 코드에서 문제의 원인이라고 의심되는 지점은 두 가지였습니다.
1. 경로 권한을 설정하는 authorizeHttpRequests((authorize) ->...)
- authorizeHttpRequests()의 RequestMatcher는 객체의 요청 경로를 일치시키는 데 사용됩니다. 문제가 되었던 경로에 오타가 없고, "permitAll()"로 인해 권한이 없어도 접근이 가능하도록 설정되어 있습니다. 이 부분에는 문제없었습니다.
2. CORS 설정을 지정하는 .cors((cors) -> cors.configurationSource(corsConfigurationSource()))
- 해당 메서드는 CORS 설정을 나타냅니다. 우선 코드를 보시죠. 아래는 CorsConfigurationSource를 설정하는 코드입니다.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); //(1)
configuration.setAllowCredentials(true); //(2)
configuration.setAllowedOrigins(List.of("*")); //(3)
configuration.setAllowedMethods(Arrays.asList("GET","POST", "PATCH", "DELETE","OPTIONS")); // (4)
configuration.setAllowedHeaders(List.of("*")); // 해당 코드 누락으로 인한 문제 발생
configuration.setExposedHeaders(List.of("Authorization", "RefreshToken","Access-Control-Allow-Origin"));//(5)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); //(6)
source.registerCorsConfiguration("/**", configuration); // (7)
return source;
}
- (1): CORS 규약을 설정하고 반환할 객체를 생성합니다.
- (2): 브라우저에서 인증 쿠키를 포함한 요청을 허용합니다.
- (3): 모든 출처의 요청을 허용합니다. (추후 배포환경에서 제약이 필요합니다.)
- (4): 특정 메서드의 요청을 허용합니다. "*"을 써서 한 번에 모든 요청을 허용할 수 있습니다.
- (5): 클라이언트 쪽에서 접근할 수 있는 헤더를 지정합니다.
- (6): URL 패턴 기반으로 CORS 설정을 적용하는 객체를 생성합니다.
- (7): 모든 URL 패턴("/")에 앞서 정의된 CORS 설정을 적용합니다.
해결
문제는 해당 메서드가 누락되어서 pre-flight로 실제 사용할 헤더를 전달받을 수 없어서 발생했습니다. 설정을 완료하고 서버가 정상적으로 요청을 받는 것을 확인했습니다.
참고
HttpSecurity (spring-security-docs 6.2.1 API)
securityContext Deprecated, for removal: This API element is subject to removal in a future version. Returns: the SecurityContextConfigurer for further customizations Throws: Exception
docs.spring.io
https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html
CORS :: Spring Security
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security, because the pre-flight request does not contain any cookies (that is, the JSESSIONID). If the request does not contain any cookies and Spring Security is
docs.spring.io
CORS 해결, 응답 헤더 내용 누락 (Authorization) 해결 (Spring)
환경 세팅 Spring Boot + Spring SecurityHtml, CSS, JSCORS (Cross Origin Resource Sharing) 문제응답 헤더 (ex. Authorization 헤더) 내용이 누락되는 현상
velog.io
https://velog.io/@cloudlee711/CORS-%EC%99%80-preflight
CORS 와 preflight
개인공부를 위해 정리한 내용입니다 :)
velog.io