궤도

[Spring] Spring Security PostMan에서 잘 되던 api가 프론트에서 cors 에러를 띄운다면 본문

💻 현생/📋 스터디

[Spring] Spring Security PostMan에서 잘 되던 api가 프론트에서 cors 에러를 띄운다면

영이오 2021. 8. 14. 13:50

현재 프론트로 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 어노테이션을 붙여줬다. 원래 이러면 안된다는데...

시간이 없었다. 나중에 더 공부해야지


진짜 최종

 

https://www.popit.kr/curl-%EB%AA%85%EB%A0%B9%EC%96%B4%EB%A1%9C-%ED%95%98%EB%8A%94-%EC%B4%88%EA%B0%84%EB%8B%A8-cors-%ED%85%8C%EC%8A%A4%ED%8A%B8/

 

cURL 명령어로 하는 초간단 CORS 테스트 | Popit

요즘은 굳이 MSA Microservices Architecture 가 아니더라도 프론트엔드 Front-end 와 백엔드 Back-end 를  구분하여 프론트 엔드는 Anguler나 React로 개발하고 백엔드는 프론트엔드에서 사용할 API를 제공하는

www.popit.kr

이 블로그를 따라할 것이다.

 

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

 

Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’ - HTTP | MDN

The CORS request was attempted with the credentials flag set, but the server is configured using the wildcard ("*") as the value of Access-Control-Allow-Origin, which doesn't allow the use of credentials.

developer.mozilla.org

여기 쓰인대로 *로 처리하지 말고 정확히 포트를 명시하자

 

    @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://github.com/hey-dongdong/hey-dongdong/blob/develop/server/src/main/java/com/ewha/heydongdong/infra/config/WebSecurityConfig.java

 

GitHub - hey-dongdong/hey-dongdong: ☕ 헤이동동 : 생협 음료 원격 주문 서비스

☕ 헤이동동 : 생협 음료 원격 주문 서비스. Contribute to hey-dongdong/hey-dongdong development by creating an account on GitHub.

github.com

이 프로젝트는 되는 것 같은데 왜 안되는걸까?

 

악 됐다

https://medium.com/@sindepal/cors-setting-for-spring-security-39133407ed8e

 

CORS Setting for Spring Security

corsConfigurationSource 메소드의 CorsConfiguration 부분을 이해 하기 위해서는 CORS[클릭] 관련 글을 먼저 읽어주세요.

medium.com

이런저런 글을 찾아봤는데 다 나랑 코드가 비슷해서 외않되 하고 있었는데 이 블로그를 발견했다.

 

    @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

 

Spring Security CORS

CORS란? - HTTP 요청은 기본적으로 Cross-Site HTTP Requests가 가능합니다. Simple 하게 다른 도메인의 Resource를 사용하는것을 말합니다. 하지만 Cross-Site HTTP Requests는 Same Origin Policy를 적용 받기..

toycoms.tistory.com

아하 앞서 만든 CorsConfigurationSource를 cors 정책의 설정파일로 등록하는 부분이라고 한다! 이걸 안해주면 의미가 없다고 한다.

난 지금까지 의미없는 짓을 해왔던 것이다.

 

하하 아무튼 성공했다.

 

참고 블로그

링크

링크

링크

Comments