๐ง ATMS - CICD ํธ๋ฌ๋ธ์ํ (CORS 403ํธ)
์ง๋ ์๊ฐ์๋ Jenkins์์ ๋ฐฐํฌ ์๋ฒ๋ก ๋์ด๊ฐ๋ SSH ์ธ์ฆ ๋ฌธ์ ๋ฅผ ๋ค ๋จ๊ณ ๋ง์ ํด๊ฒฐํ๋ค.
๋ฐฐํฌ๋ ์ฑ๊ณต์ ์ผ๋ก ๋๋ฌ๋ค. ์ปจํ
์ด๋ 7๊ฐ ๋ชจ๋ Up ์ํ. ๊ทธ๋ฐ๋ฐ ๋ธ๋ผ์ฐ์ ๋ก ์ ์ํด์ ๋ก๊ทธ์ธ์ ์๋ํ์ ๊ณง๋ฐ๋ก ๋ ๋ฒ์งธ ๋ฏธ์คํฐ๋ฆฌ๊ฐ ๋ํ๋ฌ๋ค.
nginx ๋ก๊ทธ์๋ 403์ด ์ฐํ๋๋ฐ,
"POST /api/auth/login HTTP/1.1" 403 ... "http://10.0.20.15:19080/login"
์ ์ atms-web ์ปจํ
์ด๋ ๋ก๊ทธ์๋ ์๋ฌด ๊ฒ๋ ๋จ์ง ์์๋ค. ์์ฒญ์ด ์์ ์ปจํธ๋กค๋ฌ๊น์ง ๋๋ฌํ์ง ๋ชปํ๋ค๋ ๋ป์ด๋ค.
์ธ์ฆ(JWT) ๋ฌธ์ ์ธ ์ค ์๊ณ ํ ํฐ ๋ฐ๊ธ ๋ก์ง๋ถํฐ ์์ฌํ๋ค. ํ์ง๋ง ๋ก๊ทธ์ธ API ์์ฒด๊ฐ ํธ์ถ๋ ํ์ ์กฐ์ฐจ ์๋ค๋ ๊ฒ ์ด์ํ๋ค โ ์ธ์ฆ ์ด์ ๋จ๊ณ์์ ๋งํ๊ณ ์๋ค.
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์ผ๋ก ๋์์๊ณ , ์ปจํ ์ด๋ ๋ก๊ทธ์๋ ์์ฒญ์ด ์ ์์ ์ผ๋ก ์ฐํ๊ธฐ ์์ํ๋ค.
๊ฒฐ๋ก
"๋ก๊ทธ๊ฐ ์ ๋จ๋๋ค = ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์ ๋ฌธ์ ๊ฐ ์๋ค"๊ฐ ์๋๋ผ, ์คํ๋ ค ํํฐ ์ฒด์ธ์ฒ๋ผ ์ปจํธ๋กค๋ฌ ์ด์ ๋จ๊ณ์์ ๋งํ๊ณ ์๋ค๋ ์ ํธ์ผ ์ ์๋ค๋ ๊ฑธ ๋ฐฐ์ ๋ค. AI์๊ฒ ์ฆ์์ ๋์ง๊ณ "์ด๋ ๊ณ์ธต์์ ๋งํ๊ณ ์๋์ง"๋ถํฐ ์ขํ๋๊ฐ ๊ฒ ์ฝ์ง ์๊ฐ์ ํฌ๊ฒ ์ค์ฌ์คฌ๋ค.
์ด๋ ๊ฒ ํ์ดํ๋ผ์ธ ๊ตฌ์ฑ โ SSH ์ธ์ฆ โ CORS๊น์ง, ATMS์ CI/CD ๊ตฌ์ถ๊ณผ ์ด๊ธฐ ํธ๋ฌ๋ธ์ํ ์ ๋ง๋ฌด๋ฆฌํ๋ค. Prometheus + Grafana๋ก ๋ชจ๋ํฐ๋ง์ ๋ถ์ด๋ ์ด์ผ๊ธฐ๋ ๋ค์ ๊ธฐํ์ ์ ๋ฆฌํด๋ณธ๋ค.