TroubleShooting

[TroubleShooting] ์ฃผ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋“ค์„ ์šธ๊ฒŒ ๋งŒ๋“œ๋Š” CORS ์—๋Ÿฌ

soeun2537 2024. 12. 3. 16:31

๐Ÿ‘€ ํ˜„์žฌ ์ƒํ™ฉ ๋ฐ ๋ฐฐ๊ฒฝ ์„ค๋ช…

๐Ÿ”ฝ SecurityConfig API ์š”์ฒญ ํ๋ฆ„

ํ˜„์žฌ API ์š”์ฒญ์€ ๊ณต๊ฐœ ์ ‘๊ทผ ํ•„ํ„ฐ ์ฒด์ธ๊ณผ ์ธ์ฆ์ด ํ•„์š”ํ•œ ํ•„ํ„ฐ ์ฒด์ธ, ์–ด๋“œ๋ฏผ ๊ถŒํ•œ ํ•„ํ„ฐ ์ฒด์ธ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ๊ฑฐ์น˜๊ฒŒ ๋˜์–ด์žˆ๋‹ค.

  1. ๊ณต๊ฐœ ์ ‘๊ทผ ํ•„ํ„ฐ ์ฒด์ธ: ๋กœ๊ทธ์ธ ์—†์ด๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  2. ์ธ์ฆ์ด ํ•„์š”ํ•œ ํ•„ํ„ฐ ์ฒด์ธ: ๋กœ๊ทธ์ธ์ด ๋ฐ˜๋“œ์‹œ ์š”๊ตฌ๋˜๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  3. ์–ด๋“œ๋ฏผ ๊ถŒํ•œ ํ•„ํ„ฐ ์ฒด์ธ: ์–ด๋“œ๋ฏผ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

๐Ÿ”ฝ CORS ์„ค์ •

Spring Security์—์„œ CORS๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  1. CorsFilter Bean ๋“ฑ๋ก
  2. CorsConfigurationSource Bean ๋“ฑ๋ก

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” CorsConfigurationSource๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ CORS๋ฅผ ์„ค์ •ํ–ˆ๋‹ค.

 

โ–ถ ๊ตฌํ˜„ ์ฝ”๋“œ

๐Ÿ”ฝ SecurityConfig ํด๋ž˜์Šค: ํ•„ํ„ฐ ์ฒด์ธ

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    ...

    // ๊ณต๊ฐœ ์ ‘๊ทผ ํ•„ํ„ฐ ์ฒด์ธ
    @Bean
    @Order(1)
    public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
        defaultSecuritySetting(http);
        http
                .securityMatchers(matcher -> matcher
                        .requestMatchers(publicRequestMatchers()))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(publicRequestMatchers()).permitAll()
                        .anyRequest().authenticated()
                );
        return http.build();
    }

    // ์ธ์ฆ ๋ฐ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ํ•„ํ„ฐ ์ฒด์ธ
    @Bean
    @Order(2)
    public SecurityFilterChain authenticatedFilterChain(HttpSecurity http) throws Exception {
        defaultSecuritySetting(http);
        http
                .securityMatchers(matcher -> matcher
                        .requestMatchers(authenticatedRequestMatchers()))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(authenticatedRequestMatchers())
                        .hasAnyAuthority(RoleType.ROLE_USER.name(), RoleType.ROLE_ADMIN.name())
                        .anyRequest().authenticated())
                .exceptionHandling(exception -> exception
                        .accessDeniedHandler(customAccessDeniedHandler))
                .addFilterBefore(new JwtFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    // ์–ด๋“œ๋ฏผ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ํ•„ํ„ฐ ์ฒด์ธ
    @Bean
    @Order(3)
    public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {
        defaultSecuritySetting(http);
        http
                .securityMatchers(matcher -> matcher
                        .requestMatchers(adminRequestMatchers()))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(adminRequestMatchers())
                        .hasAuthority(RoleType.ROLE_ADMIN.name())
                        .anyRequest().authenticated())
                .exceptionHandling(exception -> exception
                        .accessDeniedHandler(customAccessDeniedHandler))
                .addFilterBefore(new JwtFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    // ๊ณต๊ฐœ ์ ‘๊ทผ endpoint
    private RequestMatcher[] publicRequestMatchers() { ... }

    // ์ธ์ฆ ๋ฐ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ endpoint
    private RequestMatcher[] authenticatedRequestMatchers() { ... }

    // ์–ด๋“œ๋ฏผ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ endpoint
    private RequestMatcher[] adminRequestMatchers() { ... }

๐Ÿ”ฝ SecurityConfig ํด๋ž˜์Šค: CORS ์„ค์ •

    // Spring Security ๊ธฐ๋ณธ ์„ค์ • ๋ฉ”์„œ๋“œ
    private void defaultSecuritySetting(HttpSecurity http) throws Exception {
        http
                // CORS ์„ค์ •
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
				...
    }

    // CORS ์„ค์ •
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods( ... );
        configuration.setAllowedHeaders( ... );
        configuration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**", configuration);

        return source;
    }
}

 

 

๐Ÿšจ ๋ฌธ์ œ ์ƒํ™ฉ

โ–ถ 1. Allow Origin์— ์™€์ผ๋“œ์นด๋“œ(*) ์‚ฌ์šฉ ๊ด€๋ จ ์—๋Ÿฌ

CORS ์„ค์ •์—์„œ ํ—ˆ์šฉ ์ถœ์ฒ˜(Allow Origin)์— ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์‚ฌ์šฉํ–ˆ๋”๋‹ˆ, ๋ชจ๋“  ์š”์ฒญ์—์„œ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์„œ๋ฒ„ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•œ ๊ฒฐ๊ณผ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

โ–ถ 2. Preflight ์š”์ฒญ ์—๋Ÿฌ

CORS๋ฅผ ์„ค์ •ํ•œ ์ดํ›„, ๋ธŒ๋ผ์šฐ์ €์—์„œ Preflight ์š”์ฒญ(OPTIONS ๋ฉ”์„œ๋“œ) ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

โ–ถ 3. Preflight ์„ค์ • ์ดํ›„์—๋„ ์—ฌ์ „ํžˆ ๋ฐœ์ƒํ•˜๋Š” Preflight ์š”์ฒญ ์—๋Ÿฌ

Preflight ์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ ์„ค์ •ํ•œ ์ดํ›„์—๋„, ๊ณ„์†ํ•ด์„œ Prefiglht ์š”์ฒญ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด JwtFilter์—์„œ OPTIONS ๋ฉ”์„œ๋“œ๊ฐ€ ๋„๋‹ฌํ•  ๊ฒฝ์šฐ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋„๋ก ์„ค์ •ํ–ˆ์œผ๋‚˜, ํ•ด๋‹น ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋˜์ง€ ์•Š์•˜๋‹ค. ์ด๋ฅผ ํ†ตํ•ด Preflight ์š”์ฒญ์ด ์• ์ดˆ์— Spring Security ํ•„ํ„ฐ ์ฒด์ธ์— ๋„๋‹ฌํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ •์ƒ์ ์ธ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋˜์–ด์•ผ ํ•œ๋‹ค.

 

 

โœ๏ธ ์›์ธ ๋ถ„์„

โ–ถ 1. Allow Origin์— ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ์ด์œ ?

CORS ์„ค์ •์—์„œ Access-Control-Allow-Origin ํ—ค๋”์— ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๋ชจ๋“  ๋„๋ฉ”์ธ์—์„œ์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Access-Control-Allow-Credentials: true๊ฐ€ ์„ค์ •๋œ ์š”์ฒญ์—์„œ๋Š” ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ์™€์ผ๋“œ์นด๋“œ๋Š” ๋ชจ๋“  ๋„๋ฉ”์ธ์—์„œ ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” API์˜ ๊ฒฝ์šฐ ๋ณด์•ˆ ์ทจ์•ฝ์ ์œผ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, ์ธ์ฆ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์š”์ฒญ์—์„œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ํ—ˆ์šฉ๋œ Origin์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

โ–ถ 2. Preflight ์š”์ฒญ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ 

Preflight
๋ธŒ๋ผ์šฐ์ €๊ฐ€ CORS ์ •์ฑ…์„ ์ ์šฉํ•  ๋•Œ ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ์‹ค์ œ ์š”์ฒญ ์ „์— ์„œ๋ฒ„์— ํ™•์ธ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ, HTTP OPTIONS ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๐Ÿ”ฝ Preflight ์š”์ฒญ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ 

Spring ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด, CORS๋Š” Spring Security๋ณด๋‹ค ๋จผ์ € ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” Preflight ์š”์ฒญ์ด ์ฟ ํ‚ค(JESESSIONID)๋‚˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. Preflight ์š”์ฒญ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„์˜ CORS ์ •์ฑ…์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ณด๋‚ด๋Š” ํŠน์ˆ˜ํ•œ ์š”์ฒญ์œผ๋กœ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด๋‚˜ ์ธ์ฆ/๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•œ ์š”์ฒญ์ด ์•„๋‹ˆ๋‹ค.

๋งŒ์•ฝ Spring Security๊ฐ€ ๋จผ์ € ์„ค์ •๋  ๊ฒฝ์šฐ, Spring Security๊ฐ€ Preflight ์š”์ฒญ๋„ ์ผ๋ฐ˜ ๋น„์ฆˆ๋‹ˆ์Šค ์š”์ฒญ์œผ๋กœ ๊ฐ„์ฃผํ•ด ํ•„ํ„ฐ ์ฒด์ธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ๋‹ค. ์ด๋Š” Preflight ์š”์ฒญ์ด ์–ด๋– ํ•œ ํ•„ํ„ฐ ์ฒด์ธ์˜ ์—”๋“œํฌ์ธํŠธ์™€๋„ ๋งค์นญ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ, Preflight ์š”์ฒญ์€ ๋‹จ์ˆœํžˆ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ CORS ์ •์ฑ…์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ณด๋‚ด๋Š” ์š”์ฒญ์ด๋ฏ€๋กœ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด๋‚˜ ์ธ์ฆ/๊ถŒํ•œ ๊ฒ€์‚ฌ ๋Œ€์ƒ์ด ๋˜์–ด์„œ๋Š” ์•ˆ ๋˜๊ธฐ ๋•Œ๋ฌธ์— CORS ์ฒ˜๋ฆฌ๋Š” Spring Security๋ณด๋‹ค ์šฐ์„ ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•œ๋‹ค.

๐Ÿ”ฝ Spring Security์—์„œ Preflight ์š”์ฒญ ์„ค์ • ๋ฐฉ๋ฒ•: ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ•

์•„๋ž˜๋Š” Preflight ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ • ๋ฐฉ๋ฒ•์ด๋‹ค.

public class SecurityConfig {

    ...
    
    // Spring Security ๊ธฐ๋ณธ ์„ค์ • ๋ฉ”์„œ๋“œ
    private void defaultSecuritySetting(HttpSecurity http) throws Exception {
        http
                // CORS ์„ค์ •
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                // Preflight ์„ค์ • (ํ‹€๋ฆฐ ์„ค์ •์ด๋‹ˆ ์ฐธ๊ณ  X)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll())
    }
}

๊ทธ๋Ÿฌ๋‚˜, ์ด๋Š” ํ‹€๋ฆฐ ์„ค์ • ๋ฐฉ๋ฒ•์ด๋‹ค. 3๋ฒˆ์—์„œ ์ด ์„ค์ •์˜ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„  ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์ž.

 

โ–ถ 3. Preflight ์„ค์ • ์ดํ›„์—๋„ ์—ฌ์ „ํžˆ Preflight ์š”์ฒญ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ 

๐Ÿ”ฝ CorsFilter๊ฐ€ ์ธ์ฆ ํ•„ํ„ฐ ์ดํ›„์— ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š”๊ฐ€? 

cors() ๋ฉ”์„œ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ CorsFilter๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ง์ ‘ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ๋ถ„์„ํ–ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” CorsFilter๊ฐ€ ์ธ์ฆ์„ ์š”๊ตฌํ•˜๋Š” ํ•„ํ„ฐ๋ณด๋‹ค ๋’ค์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ์–ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์œผ๋กœ ์ถ”์ธกํ–ˆ๋‹ค. ์•„๋ž˜๋Š” Spring Security์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ํ•„ํ„ฐ๋“ค์ด๋‹ค.

ํ•„ํ„ฐ ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋  ๋•Œ, ๋“ฑ๋ก๋œ ํ•„ํ„ฐ์™€ ๊ทธ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ถœ๋ ฅํ•ด ๋ณด์•˜๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ, ์ธ์ฆ์„ ์š”๊ตฌํ•˜๋Š” ๋ชจ๋“  ํ•„ํ„ฐ๊ฐ€ CorsFilter ๋’ค์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ์—ˆ์Œ์„ ํ™•์ธํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ด๋ฒˆ ๋ฌธ์ œ๋Š” ํ•„ํ„ฐ ์ˆœ์„œ์˜ ๋ฌธ์ œ๋Š” ์•„๋‹ˆ์—ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿ”ฝ Spring Security์—์„œ Preflight ์š”์ฒญ ์„ค์ • ๋ฐฉ๋ฒ•

Spring Security ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ •๋…ํ•œ ๊ฒฐ๊ณผ, ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋ฌธ์ œ๋Š” securityMatchers()์™€ authorizeHttpRequests()์˜ ์ฐจ์ด๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๊ณ  ์‚ฌ์šฉํ•œ ๋ฐ ์žˆ์—ˆ๋‹ค.

Spring Security๋Š” HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ํ•„ํ„ฐ ์ฒด์ธ์„ ํ™œ์šฉํ•˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•„ํ„ฐ ์ฒด์ธ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฐ ํ•„ํ„ฐ ์ฒด์ธ์€ ํŠน์ • ์š”์ฒญ ๊ฒฝ๋กœ์—๋งŒ ์ ์šฉ๋˜๋ฉฐ, ์ด ์š”์ฒญ ๊ฒฝ๋กœ์™€ ํ•„ํ„ฐ ์ฒด์ธ์„ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ• ์„ securityMatchers()์™€ authorizeHttpRequests()๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  • securityMatchers()
    • ํŠน์ • ํ•„ํ„ฐ ์ฒด์ธ์ด ์ ์šฉ๋  ์š”์ฒญ์„ ๊ฒฐ์ •ํ•œ๋‹ค.
    • ์ฆ‰, ํŠน์ • ํ•„ํ„ฐ ์ฒด์ธ์ด ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •์ง“๋Š” ๊ธฐ์ค€์ด๋‹ค.
    • ์š”์ฒญ์ด securityMatchers() ์กฐ๊ฑด๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด, ํ•ด๋‹น ํ•„ํ„ฐ ์ฒด์ธ์€ ์•„์˜ˆ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.
  • authorizeHttpRequests()
    • ํ•„ํ„ฐ ์ฒด์ธ ๋‚ด๋ถ€์—์„œ ์š”์ฒญ ํ—ˆ์šฉ/๊ฑฐ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
    • ์ฆ‰, ํ•„ํ„ฐ ์ฒด์ธ์ด ์ด๋ฏธ ์„ ํƒ๋œ ์ƒํ™ฉ์—์„œ, ์š”์ฒญ์„ ํ—ˆ์šฉํ• ์ง€/๊ฑฐ๋ถ€ํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
    • ์š”์ฒญ์ด ํ•„ํ„ฐ ์ฒด์ธ์˜ ๋งค์นญ ์กฐ๊ฑด์„ ํ†ต๊ณผํ•œ ์ดํ›„์˜ ๋™์ž‘์„ ์ •์˜ํ•œ๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ ์š”์ฒญ์ด Spring Security์˜ ํ•„ํ„ฐ ์ฒด์ธ์— ๋„๋‹ฌํ•˜๋ ค๋ฉด ๋จผ์ € securityMatchers()์˜ ์กฐ๊ฑด๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” Preflight ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •์ด securityMatchers()๊ฐ€ ์•„๋‹Œ, authorizeHttpRequests()์— ์ž‘์„ฑ๋˜์–ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ด๋‹ค. ์ด๋กœ ์ธํ•ด Preflight ์š”์ฒญ์ด ํ•„ํ„ฐ ์ฒด์ธ์— ๋„๋‹ฌํ•˜์ง€ ๋ชปํ•ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ๋‹ค.

๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด authorizeHttpRequests()๋กœ Preflight ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋Š” ๊ธ€์ด ๋งŽ์ด ์žˆ์–ด ์ด๋ฅผ ์ ์šฉํ•ด ๋ณด์•˜์œผ๋‚˜, ์ž‘๋™ํ•˜์ง€ ์•Š์•˜๋‹ค. ์ถ”ํ›„ ์กฐ์‚ฌํ•ด ๋ณด๋‹ˆ, Spring Security 5.5 ๋ฒ„์ „๋ถ€ํ„ฐ securityMatchers()์™€ authorizeHttpRequests()์˜ ์—ญํ• ์ด ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋˜์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค. (์ด ์ •๋ณด๋Š” ๋ช…ํ™•ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฐธ๊ณ ๋งŒ ๋ฐ”๋ž€๋‹ค.)

 

 

๐Ÿ”จ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

โ–ถ Allow Origin์— ์™€์ผ๋“œ์นด๋“œ(*) ์‚ฌ์šฉ ๊ธˆ์ง€

๐Ÿ”ฝ ๋ณ€๊ฒฝ ์ „

// CORS ์„ค์ •
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration configuration = new CorsConfiguration();

    configuration.setAllowedOrigins(Arrays.asList("*")); // ์™€์ผ๋“œ์นด๋“œ ์‚ฌ์šฉ
    configuration.setAllowedMethods( ... );
    configuration.setAllowedHeaders( ... );
    configuration.setAllowCredentials(true);

    source.registerCorsConfiguration("/**", configuration);

    return source;
}

๐Ÿ”ฝ ๋ณ€๊ฒฝ ํ›„

// CORS ์„ค์ •
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration configuration = new CorsConfiguration();

    // ๋ช…์‹œ์ ์ธ Origin ์‚ฌ์šฉ
    configuration.setAllowedOrigins(
            Arrays.asList(
                    "http://localhost:8080",
                    "http://127.00.1:8080",
                    "http://localhost:5173",
                    "http://127.0.0.1:5173",
                    ...
            )
    );
    configuration.setAllowedMethods( ... );
    configuration.setAllowedHeaders( ... );
    configuration.setAllowCredentials(true);

    source.registerCorsConfiguration("/**", configuration);

    return source;
}

 

โ–ถ CORS ์ ์šฉ - CorsConfigurationSource ๋ฐฉ์‹: Preflight ์š”์ฒญ ํ—ˆ์šฉ

๐Ÿ”ฝ ๋ณ€๊ฒฝ ์ „

// Spring Security ๊ธฐ๋ณธ ์„ค์ • ๋ฉ”์„œ๋“œ
private void defaultSecuritySetting(HttpSecurity http) throws Exception {
    http
            // CORS ์„ค์ •
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            // authorizeHttpRequests()์— Preflight ํ—ˆ์šฉ ์„ค์ • ์ถ”๊ฐ€
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers(CorsUtils::isPreFlightRequest).permitAll())
            ...
}

๐Ÿ”ฝ ๋ณ€๊ฒฝ ํ›„

// Spring Security ๊ธฐ๋ณธ ์„ค์ • ๋ฉ”์„œ๋“œ
private void defaultSecuritySetting(HttpSecurity http) throws Exception {
    http
            // CORS ์„ค์ •
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            // securityMatchers()์— Preflight ํ—ˆ์šฉ ์„ค์ • ์ถ”๊ฐ€
            .securityMatchers(authorize -> authorize
                    .requestMatchers(CorsUtils::isPreFlightRequest).permitAll())
            ...
}

 

โ–ถ CORS ์ ์šฉ - CorsFilter ๋ฐฉ์‹

์‚ฌ์‹ค ๊ฐ€์žฅ ์‰ฝ๊ณ  ๋น ๋ฅธ ๋ฐฉ๋ฒ•์€ CorsFilter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ ์ด ๋ฐฉ์‹์„ ๊ฐ€์žฅ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๊ท€์ฐฎ๊ฒŒ Preflight ์š”์ฒญ ์„ค์ •์„ ๋”ฐ๋กœ ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.

๐Ÿ”ฝ CorsFilter์—์„œ ์‚ฌ์šฉํ•˜๋Š” DefaultCorsProcessor์˜ Preflight ์š”์ฒญ ์ฒ˜๋ฆฌ ํ™•์ธ

CorsFilter์—์„œ ์‚ฌ์šฉํ•˜๋Š” DefaultCorsProcessor๋ฅผ ์ง์ ‘ ํ™•์ธํ•˜์—ฌ, Preflight ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์ง์ ‘ ํ™•์ธํ•ด ๋ณด์•˜๋‹ค. DefaultCorsProcessor ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋‹ˆ, CorsUtils.isPreFlightRequest() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด Preflight ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿ”ฝ ์„ค์ • ๋ฐฉ๋ฒ•

์•„๋ž˜๋Š” CorsFilter ์„ค์ • ์˜ˆ์‹œ๋‹ค.

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(
                Arrays.asList(
                        "http://localhost:8080",
                        "http://127.0.0.1:8080",
                        "http://localhost:5173",
                        "http://127.0.0.1:5173",
                        ...
                )
        );
        configuration.setAllowedMethods( ... );
        configuration.setAllowedHeaders( ... );
        configuration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**", configuration);
        
        return new CorsFilter(source);
    }
}

์œ„ ํด๋ž˜์Šค๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด, SecurityConfig์—์„œ ๋ณ„๋„์˜ CORS ์„ค์ •์„ ํ•˜์ง€ ์•Š์•„๋„ Spring MVC๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก๋œ CorsFilter๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ CORS ์„ค์ •์„ ์ ์šฉํ•œ๋‹ค.

Spring MVC์˜ CORS ์„ค์ • ์šฐ์„ ์ˆœ์œ„
1. CorsFilter๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ
2. CorsConfigurationSource๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ
3. Spring MVC๊ฐ€ ํด๋ž˜์Šค ํŒจ์Šค์— ์žˆ์œผ๋ฉด Spring MVC์˜ HandlerMappingIntrospector๊ฐ€ CORS ์„ค์ •์„ ์ถ”๋ก ํ•˜์—ฌ ์‚ฌ์šฉ
์ฃผ์˜ ์‚ฌํ•ญ
1. ์ž‘๋™ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, ํ•„ํ„ฐ ์ฒด์ธ์— http.addFilter(corsConfig.corsFilter())๋ฅผ ์ง์ ‘ ์ถ”๊ฐ€ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.
2. CorsFilter์™€ CorsConfigurationSource๊ฐ€ ๋‘˜ ๋‹ค Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ” ๊ฒฐ๊ณผ ๊ด€์ฐฐ

Preflight ์š”์ฒญ(OPTIONS ๋ฉ”์„œ๋“œ)์ด ์ •์ƒ์ ์œผ๋กœ ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•˜๋ฉฐ, ์—”๋“œํฌ์ธํŠธ๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 

๐Ÿ’ก ๊ณ ์ฐฐ

  • ๋“œ๋””์–ด ํ”„๋กœ์ ํŠธ ๋‚ด๋‚ด ๊ณจ๋จธ๋ฆฌ๋ฅผ ์•“๊ฒŒ ํ–ˆ๋˜ CORS ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ๊ทธ ๊ทผ๋ณธ์ ์ธ ์›์ธ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์„ฑ๊ณตํ–ˆ๋‹ค. ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€ ์‚ฝ์งˆ์„ ํ–ˆ์ง€๋งŒ, ๊ทธ ๊ณผ์ •์—์„œ Spring Security์™€ CORS์— ๋Œ€ํ•ด ๊นŠ์ด ๋ฐฐ์šฐ๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋˜์—ˆ๋‹ค. ์ด ๋ฌธ์ œ๊ฐ€ ๊ตฌ๊ธ€๋ง์„ ํ•ด๋„ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ๋งŽ์ง€ ์•Š์•„, ๋น„์Šทํ•œ ๋ฌธ์ œ๋ฅผ ๊ฒช๋Š” ๋ˆ„๊ตฐ๊ฐ€์—๊ฒŒ ๋„์›€์ด ๋ ๊นŒ ์‹ถ์–ด ์ž์„ธํžˆ ์ •๋ฆฌํ•ด ๊ธ€์„ ๋‚จ๊ธฐ๊ณ ์ž ํ–ˆ๋‹ค.
  • ์‚ฌ์‹ค ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‹œ๊ฐ„์ด ๋ถ€์กฑํ•˜๋‹ค ๋ณด๋‹ˆ, ๋ฌธ์ œ์˜ ์›์ธ์„ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•˜์ง€ ๋ชปํ•œ ์ฑ„ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•๋งŒ ์—ฌ๋Ÿฌ ๊ฐœ ๋„์ž…ํ•ด ์—๋Ÿฌ๋ฅผ ์ž ์‹œ ํ•ด๊ฒฐํ•ด ๋†จ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ •์ž‘ ์›์ธ์„ ์•Œ์ง€ ๋ชปํ•œ ์ƒํƒœ๋กœ ๋„˜์–ด๊ฐ”๋˜ ๊ฒƒ์ด ๋Š˜ ์ฐ์ฐํ•˜๊ฒŒ ๋‚จ์•„ ์žˆ์—ˆ๋‹ค. ํ”„๋กœ์ ํŠธ๊ฐ€ ๋๋‚œ ์ดํ›„ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ํ†ตํ•ด ๊ฒฐ๊ตญ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , ๊ทธ๋™์•ˆ์˜ ์‚ฝ์งˆ์ด ํ—›๋˜์ง€ ์•Š์•˜์Œ์„ ๊นจ๋‹ฌ์œผ๋ฉฐ ๋ฟŒ๋“ฏํ•จ์„ ๋А๊ผˆ๋‹ค.
  • ์ด๋ฒˆ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ณผ์ •์—์„œ Spring ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์›์ธ์„ ๋ถ„์„ํ•˜๋ฉฐ, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๊ผผ๊ผผํžˆ ํ™•์ธํ•˜๋Š” ๊ฒƒ์˜ ์ค‘์š”์„ฑ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์‹ค๊ฐํ–ˆ๋‹ค. ์ตœ๊ทผ GPT์— ์˜์กดํ–ˆ๋˜ ์ ์„ ๋ฐ˜์„ฑํ•˜๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋˜์—ˆ๋‹ค.

 

 

๐Ÿ“ ์ฐธ๊ณ  ์ž๋ฃŒ