DevGang
[Spring Boot] CORS와 Preflight에 관한 삽질 본문
처음으로 서버 사이드 랜더링이 아닌 클라이언트 사이드 랜더링 즉 프런트와 백엔드를 분리하여 룰루랄라 프로젝트를 진행하던 중 야생의 오류가 나타났다..! 바로 CORS와 Preflight⭐⭐⭐
클라이언트 측에서 보안상의 이유로 다른 출처, 즉 URL이 다른 리소스를 참고하는 것을 기본적으로 막고 있다.
따라서 클라이언트(http://lcoalhost:63342)에서 API 서버(http://localhost:8080)의 리소스를 참고하면 오류가 나온다.
해당 오류를 해결하려면 API 서버 측에서 해당 클라이언트의 URL에 대해 리소스 참고를 허용하도록 헤더(Access-Control-Allow-Origin)에 값을 넣어서 응답 요청을 보내주어야 한다.
스프링에서는 해당 기능을 아래와 같이 간단하게 지원하고 있다.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:63342");
}
}
하지만!!!
이번 프로젝트에 사용자 인증/인가를 위해 Spring Security를 사용하고 있었는데 해당 오류를 만났다.
위와 같이 헤더에 Access-Control-Allow-Origin를 보내 해결할 수 있는 경우가 제한이 있었다.
- 요청 메서드(method)는 GET, HEAD, POST 중 하나여야 한다.
- Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안 됩니다.
- Content-Type 헤더는 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나를 사용해야 합니다.
첫 번째 조건은 쉬운 조건이지만 2, 3번째 조건은 까다로운 조건이다. 나의 경우에는 2번 사항에 해당되는데 스프링 시큐리티를 사용하며 Authorization헤더를 사용하기 때문이다. 그리고 3번 조건은 많은 REST API들이 Content-Type으로 application/json를 사용하기 때문에 지키기 쉽지 않은 조건이다.
위에 3가지 조건을 만족하지 못하면 서버에 예비 요청(HTTP - OPTIONS)을 보내서 안전한지 판단한 후 본 요청을 보내는 Preflight 요청을 보낸다.
프로젝트 스프링 시큐리티 설정
package shop.fevertime.backend.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import shop.fevertime.backend.security.jwt.JwtAuthenticationEntryPoint;
import shop.fevertime.backend.security.jwt.JwtAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/challenges/**").permitAll()
.antMatchers(HttpMethod.GET, "/feeds/**").permitAll()
.antMatchers("/login/kakao").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
위에 설정에서 Preflight 요청으로 보내는 모든 HttpMethod의 OPTIONS 메서드를 거부하기 때문에 생긴 오류였다.
그래서 아래와 같이 코드를 수정하니 정상 작동했다!!! 😂😂😂
package shop.fevertime.backend.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import shop.fevertime.backend.security.jwt.JwtAuthenticationEntryPoint;
import shop.fevertime.backend.security.jwt.JwtAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**/*").permitAll() // 해당 코드 추가
.antMatchers(HttpMethod.GET, "/challenges/**").permitAll()
.antMatchers(HttpMethod.GET, "/feeds/**").permitAll()
.antMatchers("/login/kakao").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
참고 - https://beomy.github.io/tech/browser/cors/
'Study > Spring' 카테고리의 다른 글
[Spring] nGrinder으로 스프링부트 애플리케이션(AWS EB) 부하테스트 (0) | 2021.12.21 |
---|---|
[Servlet] 서블릿 (0) | 2021.07.14 |
[Spring] DispatcherServlet (0) | 2021.06.02 |
[Spring] AOP(Aspect-oriented programming) (1) | 2021.05.12 |
[Servlet&Jsp] MVC model1 vs model2 (0) | 2021.05.10 |