Backend/spring

[Spring Security] CSRF & CORS ๊ฐœ๋… ๋ฐ ์„ค์ • ๋ฐฉ๋ฒ•

dddzr 2025. 2. 23. 18:26

๐Ÿ“Œ 1. CSRF(Cross-Site Request Forgery)๋ž€?

  • ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ์—์„œ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ํ†ตํ•ด ์›์น˜ ์•Š๋Š” ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ์œ ๋„ํ•˜๋Š” ๊ณต๊ฒฉ ๋ฐฉ์‹
  • ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์€ํ–‰ ์›น์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ์—์„œ ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๊ณ„์ขŒ์ด์ฒด ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ๊ณต๊ฒฉ์ด ์„ฑ๊ณตํ•  ์ˆ˜ ์žˆ์Œ

 

โœ… 1-1. Spring Security์—์„œ CSRF ์„ค์ • ๋ฐฉ๋ฒ•

๐Ÿ”น CSRF ๋ฐฉ์–ด ํ™œ์„ฑํ™” (๊ธฐ๋ณธ๊ฐ’)

  • Spring Security์—์„œ๋Š” CSRF ๋ณดํ˜ธ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋จ
  • ํผ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ(formLogin())์„ ์‚ฌ์šฉํ•  ๋•Œ CSRF ๋ฐฉ์–ด๊ฐ€ ํ•„์š”ํ•จ
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.enable()) // ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Œ
        .authorizeHttpRequests(auth -> auth
            .anyRequest().authenticated()
        )
        .formLogin(withDefaults())
        .build();
}

 

๐Ÿ”น CSRF ๋น„ํ™œ์„ฑํ™” (API ์„œ๋ฒ„์—์„œ๋Š” ์ฃผ๋กœ ๋น„ํ™œ์„ฑํ™”)

  • REST API์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ CSRF ๋ณดํ˜ธ๊ฐ€ ํ•„์š” ์—†์Œ → API ํ˜ธ์ถœ ์‹œ ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  JWT ๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ๋ฌธ
  • POST, PUT, DELETE ์š”์ฒญ์ด ์‹คํŒจํ•˜๋ฉด 403 Forbidden ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ → CSRF ๋น„ํ™œ์„ฑํ™” ํ•„์š”
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.disable()) // API ์„œ๋ฒ„๋ผ๋ฉด ๋ณดํ†ต ๋น„ํ™œ์„ฑํ™”
        .authorizeHttpRequests(auth -> auth
            .anyRequest().permitAll()
        )
        .build();
}

 

โœ” CSRF ์„ค์ •์„ ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜:

  • 403 Forbidden: CSRF ํ† ํฐ์ด ํ•„์š”ํ•˜์ง€๋งŒ ์ œ๊ณต๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์ž˜๋ชป๋จ

 

๐Ÿ“Œ 2. CORS(Cross-Origin Resource Sharing)๋ž€?

  • ํ•œ ๋„๋ฉ”์ธ์—์„œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฐจ๋‹จํ•˜๋Š” ๋ณด์•ˆ ์ •์ฑ…
  • ์˜ˆ๋ฅผ ๋“ค์–ด, http://localhost:3000์—์„œ http://api.example.com๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด CORS ์ •์ฑ… ์œ„๋ฐ˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ

 

โœ… 2-1. Spring Security์—์„œ CORS ์„ค์ • ๋ฐฉ๋ฒ•

๐Ÿ”น CORS ํ—ˆ์šฉ ์„ค์ • (Spring MVC - Web)

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .cors(withDefaults()) // CORS ์„ค์ • ์ ์šฉ
        .csrf(csrf -> csrf.disable()) // ๋ณดํ†ต API ์„œ๋ฒ„์—์„œ๋Š” CSRF ๋น„ํ™œ์„ฑํ™”
        .authorizeHttpRequests(auth -> auth
            .anyRequest().permitAll()
        )
        .build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(List.of("http://localhost:3000")); // ํ—ˆ์šฉํ•  ์ถœ์ฒ˜
    configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    configuration.setAllowedHeaders(List.of("*"));
    configuration.setAllowCredentials(true);

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

 

๐Ÿ”น CORS ํ—ˆ์šฉ ์„ค์ • (Spring WebFlux)

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
        .cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS ์„ค์ • ์ ์šฉ
        .csrf(csrf -> csrf.disable()) 
        .authorizeExchange(exchange -> exchange
            .anyExchange().permitAll()
        )
        .build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(List.of("http://localhost:3000"));
    configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    configuration.setAllowedHeaders(List.of("*"));
    configuration.setAllowCredentials(true);

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

 

โœ” CORS ์„ค์ •์„ ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜:

  • CORS policy ์˜ค๋ฅ˜ (Access to fetch at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy)
  • Preflight ์š”์ฒญ ์‹คํŒจ (OPTIONS ์š”์ฒญ์ด ์ฐจ๋‹จ๋จ)

 

๐Ÿ”ฅ3. CSRF vs CORS ๋น„๊ต ์ •๋ฆฌ

๊ฐœ๋… CSRF (Cross-Site Request Forgery) CORS (Cross-Origin Resource Sharing)
๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ์•…์šฉํ•œ ์š”์ฒญ ์œ„์กฐ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ API ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๋ธŒ๋ผ์šฐ์ € ์ฐจ๋‹จ
๋ณดํ˜ธ ๋Œ€์ƒ ์„œ๋ฒ„ (๋ฐฑ์—”๋“œ) ํด๋ผ์ด์–ธํŠธ (๋ธŒ๋ผ์šฐ์ €)
๊ณต๊ฒฉ ์˜ˆ์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ์—์„œ ์•…์„ฑ ์š”์ฒญ์ด ์ „์†ก๋จ localhost:3000์—์„œ api.example.com์œผ๋กœ ์š”์ฒญ ์‹œ ์ฐจ๋‹จ๋จ
Spring Security ๊ธฐ๋ณธ ์„ค์ • โœ… ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋จ โŒ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ
๋น„ํ™œ์„ฑํ™” ์‹œ ๋ฐœ์ƒํ•  ๋ฌธ์ œ 403 Forbidden ์˜ค๋ฅ˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ CORS policy ์˜ค๋ฅ˜ ๋ฐœ์ƒ
์„ค์ • ๋ฐฉ๋ฒ• csrf().disable() cors().configurationSource()

๐Ÿš€ ์ •๋ฆฌ

โœ… CSRF๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋จ → API ์„œ๋ฒ„์—์„œ๋Š” ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ 
โœ… CORS๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ → ํด๋ผ์ด์–ธํŠธ์—์„œ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐ˜๋“œ์‹œ ์„ค์ • ํ•„์š”