궤도
[Spring] Spring Security PostMan에서 잘 되던 api가 프론트에서 cors 에러를 띄운다면 본문
현재 프론트로 React를 쓰고 있다.
스프링은 8080 포트를 사용하고 리액트는...3000이던가 3001이던가 아무튼 둘 중 하나였던 것 같다.
이게 맞지 않아서 cors가 뜨는데, 이를 해결하기 위해 CorsFilter를 정의해야 한다.
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
//암호화에 필요한 PasswordEncoder를 Bean 등록
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//authenticationManager를 Bean 등록
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
//Cors
@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L); //preflight 결과를 1시간동안 캐시에 저장
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.and()
.cors()
.and()
.httpBasic().disable() //rest api만 고려하면 해제해도 되는듯?
.csrf().disable() //csrf 보안 토큰 diable 처리
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //토큰 기반 인증이라 세션은 사용하지 않음
.and()
.authorizeRequests() //요청에 대한 권한 체크
.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/user/**").hasRole("USER")
.antMatchers("/**").permitAll() //그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class); //JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣음
}
}
JWT 할 때 만들어둔 WebSecurityConfig 파일이다.
@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
이러면...안되겠지만 난 귀찮아서 그냥 다 *로 허용해버렸다. setAllowedOrigins 정도는 접근할 수 있는 url을 한정하는게 좋을 지도...
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.httpBasic().disable() //rest api만 고려하면 해제해도 되는듯?
.csrf().disable() //csrf 보안 토큰 diable 처리
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //토큰 기반 인증이라 세션은 사용하지 않음
.and()
.authorizeRequests() //요청에 대한 권한 체크
.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/user/**").hasRole("USER")
.antMatchers("/**").permitAll() //그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class); //JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣음
}
이것도 기존 코드에서 위에 cors().and()를 추가했다.
CORS 설정에는 다양한 방법이 있는데 나처럼 Spring Security를 사용했으면 이렇게 해야한다.
근데 여기까지 하니 이번엔 prefilght가 문제다.
여기저기 찾아봤는데 솔직히 잘 모르겠고 preflight를 같이 보내야 문제가 안생기는 그거라 설정을 해야한다고 한다.
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
//암호화에 필요한 PasswordEncoder를 Bean 등록
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//authenticationManager를 Bean 등록
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
//Cors
@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L); //preflight 결과를 1시간동안 캐시에 저장
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.and()
.cors()
.and()
.httpBasic().disable() //rest api만 고려하면 해제해도 되는듯?
.csrf().disable() //csrf 보안 토큰 diable 처리
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //토큰 기반 인증이라 세션은 사용하지 않음
.and()
.authorizeRequests() //요청에 대한 권한 체크
.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/user/**").hasRole("USER")
.antMatchers("/**").permitAll() //그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class); //JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣음
}
}
그래서 최종은 이렇게 된다.
아니었다 이래도 안된다
결국 컨트롤러 마다 @CrossOrgin 어노테이션을 붙여줬다. 원래 이러면 안된다는데...
시간이 없었다. 나중에 더 공부해야지
진짜 최종
이 블로그를 따라할 것이다.
curl \
--verbose \
--request OPTIONS \
http://localhost:8080/character \ #테스트 할 api
--header 'Origin: http://localhost:3000' \ #request를 날릴 포트
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET' #요청 메서드
터미널에 이대로 명령하면 4xx대의 결과가 나와야한다. 왜냐하면 Cors 설정을 달리 하지 않은 상태기 때문이다.
그럼 request의 포트번호가 서버와 동일한 8080이라면?
당연히 제대로 된다!
그럼 이렇게 @CrossOrigin 설정을 하면 어떻게 될까?
된다! 하지만 오늘의 목표는 여기서 멈추지 않는다. 왜냐하면 모든 메서드마다 CrossOrigin 어노테이션을 붙이는건 귀찮기 떄문이다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
여기 쓰인대로 *로 처리하지 말고 정확히 포트를 명시하자
@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://localhost:3000");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L); //preflight 결과를 1시간동안 캐시에 저장
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
안되네...
이 프로젝트는 되는 것 같은데 왜 안되는걸까?
악 됐다
https://medium.com/@sindepal/cors-setting-for-spring-security-39133407ed8e
이런저런 글을 찾아봤는데 다 나랑 코드가 비슷해서 외않되 하고 있었는데 이 블로그를 발견했다.
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.and()
.cors().configurationSource(configurationSource())
.and()
.httpBasic().disable() //rest api만 고려하면 해제해도 되는듯?
.csrf().disable() //csrf 보안 토큰 diable 처리
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //토큰 기반 인증이라 세션은 사용하지 않음
.and()
.authorizeRequests() //요청에 대한 권한 체크
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/**").permitAll() //그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class); //JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣음
}
여기서 cors() 뒤에 위에서 만든 configurationSource를 넣어줬다. 왜 이걸 붙이면 작동하는지는 알 수 없다
https://toycoms.tistory.com/37
아하 앞서 만든 CorsConfigurationSource를 cors 정책의 설정파일로 등록하는 부분이라고 한다! 이걸 안해주면 의미가 없다고 한다.
난 지금까지 의미없는 짓을 해왔던 것이다.
하하 아무튼 성공했다.
참고 블로그