๐Ÿง ATMS - CICD ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…(CORS 403ํŽธ)



์ง€๋‚œ ์‹œ๊ฐ„์—๋Š” Jenkins์—์„œ ๋ฐฐํฌ ์„œ๋ฒ„๋กœ ๋„˜์–ด๊ฐ€๋Š” SSH ์ธ์ฆ ๋ฌธ์ œ๋ฅผ ๋„ค ๋‹จ๊ณ„ ๋งŒ์— ํ•ด๊ฒฐํ–ˆ๋‹ค.

โ–ถ ๐Ÿง ATMS - CICD ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…(SSH Permission DeniedํŽธ)

๋ฐฐํฌ๋Š” ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚ฌ๋‹ค. ์ปจํ…Œ์ด๋„ˆ 7๊ฐœ ๋ชจ๋‘ Up ์ƒํƒœ. ๊ทธ๋Ÿฐ๋ฐ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ ‘์†ํ•ด์„œ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•˜์ž ๊ณง๋ฐ”๋กœ ๋‘ ๋ฒˆ์งธ ๋ฏธ์Šคํ„ฐ๋ฆฌ๊ฐ€ ๋‚˜ํƒ€๋‚ฌ๋‹ค.



์ฆ์ƒ โ€” 403์ธ๋ฐ, ๋ฐฑ์—”๋“œ ๋กœ๊ทธ๊ฐ€ ํ•œ ์ค„๋„ ์—†๋‹ค


nginx ๋กœ๊ทธ์—๋Š” 403์ด ์ฐํžˆ๋Š”๋ฐ,

"POST /api/auth/login HTTP/1.1" 403 ... "http://10.0.20.15:19080/login"

์ •์ž‘ atms-web ์ปจํ…Œ์ด๋„ˆ ๋กœ๊ทธ์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ๋‚จ์ง€ ์•Š์•˜๋‹ค. ์š”์ฒญ์ด ์•„์˜ˆ ์ปจํŠธ๋กค๋Ÿฌ๊นŒ์ง€ ๋„๋‹ฌํ•˜์ง€ ๋ชปํ–ˆ๋‹ค๋Š” ๋œป์ด๋‹ค.

๊ฐ€์žฅ ํ—ท๊ฐˆ๋ ธ๋˜ ์ง€์ 

์ธ์ฆ(JWT) ๋ฌธ์ œ์ธ ์ค„ ์•Œ๊ณ  ํ† ํฐ ๋ฐœ๊ธ‰ ๋กœ์ง๋ถ€ํ„ฐ ์˜์‹ฌํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋กœ๊ทธ์ธ API ์ž์ฒด๊ฐ€ ํ˜ธ์ถœ๋œ ํ”์ ์กฐ์ฐจ ์—†๋‹ค๋Š” ๊ฒŒ ์ด์ƒํ–ˆ๋‹ค โ€” ์ธ์ฆ ์ด์ „ ๋‹จ๊ณ„์—์„œ ๋ง‰ํžˆ๊ณ  ์žˆ๋‹ค.



AI์™€ ํ•จ๊ป˜ ์š”์ฒญ ํ๋ฆ„์„ ํ•œ ์ค„์”ฉ ๋”ฐ๋ผ๊ฐ€ ๋ณด๊ธฐ


Claude Code์—๊ฒŒ ์ฆ์ƒ์„ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ–ˆ๋‹ค.

"๋กœ๊ทธ์ธ API ํ˜ธ์ถœ ์‹œ 403์ด ๋œจ๋Š”๋ฐ, Spring ์ชฝ ๋กœ๊ทธ๊ฐ€ ์ „ํ˜€ ์•ˆ ๋‚จ์•„์š”. ์ปจํŠธ๋กค๋Ÿฌ ์ง„์ž… ์ „์— ๋ญ”๊ฐ€ ๋ง‰๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ ์–ด๋””๋ถ€ํ„ฐ ๋ด์•ผ ํ• ๊นŒ์š”?"

๋Œ์•„์˜จ ๋ฐฉํ–ฅ์€ ๋ช…ํ™•ํ–ˆ๋‹ค โ€” "Spring Security์˜ Filter Chain์„ ์˜์‹ฌํ•˜๋ผ. ์ปจํŠธ๋กค๋Ÿฌ๋Š” ํ•„ํ„ฐ ์ฒด์ธ์„ ํ†ต๊ณผํ•ด์•ผ๋งŒ ์‹คํ–‰๋˜๊ณ , ๊ทธ์ค‘์—์„œ๋„ CorsFilter๋Š” ์ฒด์ธ ๋งจ ์•ž์ชฝ์—์„œ ๋™์ž‘ํ•œ๋‹ค."

๊ทธ ๋ง์„ ๋”ฐ๋ผ ์š”์ฒญ ํ๋ฆ„์„ ๊ทธ๋ ค๋ดค๋‹ค.

graph TD
	A[๋ธŒ๋ผ์šฐ์ € fetch POST, Origin: http://10.0.20.15:19080]
	B[nginx ํ”„๋ก์‹œ - Origin ํ—ค๋” ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ]
	C[Spring Security CorsFilter]
	D{Origin์ด ํ—ˆ์šฉ ๋ชฉ๋ก์— ์žˆ๋Š”๊ฐ€}
	E[์ปจํŠธ๋กค๋Ÿฌ ์ง„์ž…]
	F[403 ์ฆ‰์‹œ ๋ฐ˜ํ™˜ - ๋กœ๊ทธ ์—†์Œ]

	A --> B --> C --> D
	D -->|Yes| E
	D -->|No| F

SecurityConfig.java๋ฅผ ์—ด์–ด๋ณด๋‹ˆ ์˜ˆ์ƒ๋Œ€๋กœ์˜€๋‹ค.

// ํ—ˆ์šฉ Origin์ด ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ์—ˆ๋‹ค
configuration.setAllowedOrigins(List.of(
    "http://localhost:3000",
    "http://localhost:5173"
));

๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ Origin๋งŒ ๋“ฑ๋ก๋˜์–ด ์žˆ์—ˆ๊ณ , ์‹ค์ œ ์ ‘์† ์ฃผ์†Œ(http://10.0.20.15:19080)๋Š” ๋ชฉ๋ก์— ์—†์—ˆ๋‹ค. CorsFilter๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ๋ณด๋‹ค ๋จผ์ € ์š”์ฒญ์„ ๋Š์–ด๋ฒ„๋ ธ๊ธฐ ๋•Œ๋ฌธ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ๊ฐ€ ๋‚จ์„ ๊ธฐํšŒ์กฐ์ฐจ ์—†์—ˆ๋˜ ๊ฒƒ์ด๋‹ค.



ํ•ด๊ฒฐ โ€” ํ•˜๋“œ์ฝ”๋”ฉ์„ ๊ฑท์–ด๋‚ด๊ณ  ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ


๋กœ์ปฌ/์„œ๋ฒ„ ํ™˜๊ฒฝ๋งˆ๋‹ค Origin์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜๋ฐ–์— ์—†์œผ๋‹ˆ, ์ฝ”๋“œ์—์„œ ์™„์ „ํžˆ ๋นผ์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…ํ•˜๋„๋ก ๋ฐ”๊ฟจ๋‹ค.

โ‘  SecurityConfig.java

@Value("${cors.allowed-origins:http://localhost:3000,http://localhost:5173}")
private String corsAllowedOrigins;

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    List<String> origins = Arrays.stream(corsAllowedOrigins.split(","))
            .map(String::trim)
            .toList();
    configuration.setAllowedOrigins(origins);
    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;
}

โ‘ก docker-compose.yml โ€” atms-web ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€

environment:
  CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:5173}

โ‘ข ์„œ๋ฒ„ .env โ€” ์‹ค์ œ ์ ‘์† ์ฃผ์†Œ ์ง€์ •

CORS_ALLOWED_ORIGINS=http://10.0.20.15:19080

atms-web ์ปจํ…Œ์ด๋„ˆ๋งŒ ์žฌ๋นŒ๋“œยท์žฌ์‹œ์ž‘ํ•˜๋‹ˆ ๊ทธ์ œ์„œ์•ผ ๋กœ๊ทธ์ธ ์‘๋‹ต์ด 200์œผ๋กœ ๋Œ์•„์™”๊ณ , ์ปจํ…Œ์ด๋„ˆ ๋กœ๊ทธ์—๋„ ์š”์ฒญ์ด ์ •์ƒ์ ์œผ๋กœ ์ฐํžˆ๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค.



๊ฒฐ๋ก 

  • JWT ์ธ์ฆ ๋กœ์ง ๋ฌธ์ œ X
  • nginx ํ”„๋ก์‹œ ์„ค์ • ๋ฌธ์ œ X
  • Spring Security CorsFilter์˜ ํ•˜๋“œ์ฝ”๋”ฉ๋œ Origin ๋ชฉ๋ก O

"๋กœ๊ทธ๊ฐ€ ์•ˆ ๋‚จ๋Š”๋‹ค = ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ, ์˜คํžˆ๋ ค ํ•„ํ„ฐ ์ฒด์ธ์ฒ˜๋Ÿผ ์ปจํŠธ๋กค๋Ÿฌ ์ด์ „ ๋‹จ๊ณ„์—์„œ ๋ง‰ํžˆ๊ณ  ์žˆ๋‹ค๋Š” ์‹ ํ˜ธ์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค. AI์—๊ฒŒ ์ฆ์ƒ์„ ๋˜์ง€๊ณ  "์–ด๋А ๊ณ„์ธต์—์„œ ๋ง‰ํžˆ๊ณ  ์žˆ๋Š”์ง€"๋ถ€ํ„ฐ ์ขํ˜€๋‚˜๊ฐ„ ๊ฒŒ ์‚ฝ์งˆ ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ์ค„์—ฌ์คฌ๋‹ค.



์ด๋ ‡๊ฒŒ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ โ†’ SSH ์ธ์ฆ โ†’ CORS๊นŒ์ง€, ATMS์˜ CI/CD ๊ตฌ์ถ•๊ณผ ์ดˆ๊ธฐ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ๋งˆ๋ฌด๋ฆฌํ•œ๋‹ค. Prometheus + Grafana๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ๋ถ™์ด๋Š” ์ด์•ผ๊ธฐ๋Š” ๋‹ค์Œ ๊ธฐํšŒ์— ์ •๋ฆฌํ•ด๋ณธ๋‹ค.