Server Side Template ๋ฐฉ์์ด ์๋ Front์ Back ์ผ๋ก ๋๋ ์ ์ธํ๋ผ๋ฅผ ๊ตฌ์ฑํด๋ณธ ๊ฒฝํ์ด ์๋ ์ฌ๋๋ค์๊ฒ๋ Cors๊ฐ ๋งค์ฐ ์น์ํ ์ ์๋ค.
ํ์ฌ ๊ฐ๋ฐ ํ๋ฆ์์ ์น ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๊ฐ Cors ๋ฅผ ๋ง๋ ํ๋ฅ ์ ๊ฐํ 100% ๋ผ๊ณ ํด๋ ๊ณผ์ธ์ด ์๋๋ค.
์ค๋์ CORS ๊ฐ ๋ฌด์์ธ์ง ์์๋ณด๋ฉฐ ๋, Spring Boot Application ์์๋ ์ด๋ป๊ฒ ํด๋น ์ ์ฑ ์ ํด๊ฒฐํ ์ ์๋์ง ์์๋ณด์.
CORS ํด๊ฒฐํ๊ธฐ
์ฐ์ ๊ฐ์ฅ ๋จผ์ ๋ง ํ๊ณ ์ถ์ ๊ฒ์
CORS ๋ ์๋ฌ๋ ์ค๋ฅ๊ฐ ์๋๋ค. Cross-Origin Resource Sharing Policy ์ด๋ค.
์ฒ์ ๋ด๊ฐ Front ์ Back ์ ๋๋ ์ ๊ฐ๋ฐ์ ํ ๋์๋ ๋จ์ํ Front ์์ fetch ์์ฒญ์ผ๋ก Back ์ ๋ฐ๋ผ๋ณด๊ณ ๋ ๋ฆฌ๋ฉด ๋ ์ค ์์๋๋ฐ, ์์์ ๋ณด์ด๋ ์๋ฌ๊ฐ ๋ฐ์ํ๊ธฐ์ ์ ๋ง ์ง์ฆ๋๊ณ ๊ท์ฐฎ์ ์๋ฌ ์ฏค ์ผ๋ก ์๊ฐ์ ํ์๋ค.
ํ์ง๋ง CORS ๋ ๊ท์ฐฎ์ ๋ฟ ํ๋์ ๋ณด์ ์ ์ฑ ์ด๋ค.
CORS ์ ๋ํ ์์ธํ ์ด์ผ๊ธฐ๋ ํด๋น ๋ธ๋ก๊ทธ Cross Origin Resource Sharing ์ด๋? ์์ ํ์ธํ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ณด์ ์ ์ฑ ์ด ๋ฐ์ํ๋ ์ด์ ๋ฅผ ๊ฐ๋จํ ์ด์ผ๊ธฐ ํ์๋ฉด, SOP ๋๋ฌธ์ด๋ค.
SOP ๋?
SOP ๋ Same Origin Policy ๋ก, ๋์ผํ ์ถ์ฒ์ Origin ๋ง ๋ฆฌ์์ค(๋ฐ์ดํฐ)๋ฅผ ๊ณต์ ํ ์ ์๋ ๊ฒ์ด๋ค.
๋ง์ฝ ๋์ผํ ์ถ์ฒ๊ฐ ์๋๋ผ๋ฉด OPTIONS ๋ฅผ ์ด์ฉํ Preflight ๋ฅผ ์ด์ฉํด์ ์ฌ๋ฌ ๊ฒ์ฆ์ ๊ฑฐ์น๊ฒ ๋๋๋ฐ,
OPTIONS์ Preflight ์ ๋ํ ์์ธํ ์ฌํญ์ ํด๋น ๋ธ๋ก๊ทธ์ HTTP OPTIONS ํค๋์ Preflight ๊ทธ๋ฆฌ๊ณ CORS ์์ ํ์ธํ ์ ์์ต๋๋ค.
์ด ๊ฒ์ฆ์ด ๋ฐ๋ก Cors ์ธ ๊ฒ์ด๋ค.
CORS ์ ๊ฐ๋จ ํ๋ก์ฐ๋?
์ฐ๋ฆฌ๊ฐ Front Back ๊ตฌ์กฐ์์ SOP ๋ฅผ ์๋ฐํ๊ณ , ์ ์ ํ ๋ณด์ ์ธ์ฆ์ ๋ฐ๊ธฐ ์ํด์ ๋ธ๋ผ์ฐ์ ๋ ๋ค์๊ณผ ๊ฐ์ ๊ณผ์ ์ ๊ฑฐ์น๊ฒ ๋๋ค.
- GET ์์ฒญ์ธ์ง POST ์์ฒญ์ธ์ง ํ์ ํ๋ค.
- Content-Type ๊ณผ Custom HTTP Header ๋ฅผ ํ์ ํ๋ค.
- OPTIONS ์์ฒญ์ ํตํด์ ์๋ฒ๊ฐ ์ ์ ํ Access-Control-* ๋ฅผ ๊ฐ์ก๋์ง ํ์ธํ๋ค.
- ๋ง์ฝ ์ ์ ํ Access-Control ์ ๊ฐ์ก๋ค๋ฉด ์ค์ XHR์ ํธ๋ฆฌ๊ฑฐํ๋ค.
- ์ ์ ํ์ง ๋ชปํ Access-Control ๋ฅผ ๊ฐ์ก๋ค๋ฉด Error ๋ฅผ ๋ฐ์์ํจ๋ค.
์ด ํ๋ก์ฐ์์ ์ ์ ํ ๋์ฒ๋ฅผ ํ์ง ๋ชปํ๋ค๋ฉด ๋ฐ์ํ๋ ๊ฒ์ด ๋งจ ์์์ ๋ณธ CORS ์๋ฌ์ธ ๊ฒ์ด๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ์ค์ Spring Boot Application ์ผ๋ก ๋ค์ด๊ฐ๋ณด์.
Spring Boot Application ์์ CORS ํด๊ฒฐํ๊ธฐ
๋ณธ์ธ์ด ๋ง์ฝ ์ด๋ค API ์๋ฒ๋ ๊ฐ์ง๊ณ ์๋ค๋ฉด ๊ทธ ์๋ฒ๋ก ํ ์คํธํ ์ ์์ต๋๋ค. ์ ๋ ํ์ฌ ํ ํ๋ก์ ํธ์์ ์ฌ์ฉ์ค์ธ API ์๋ฒ๋ฅผ ๋ง์นจ CORS ์ด์๋ฅผ ํด๊ฒฐํด์ผ ํ๊ธฐ ๋๋ฌธ์ ํด๋น ์๋ฒ๋ก ์ค์ต์ ์ด์ด๊ฐ๊ฒ ์ต๋๋ค.
๋ณธ๋ก ์ผ๋ก ๋์๊ฐ์ CORS ๋ ์ ๋ฐ์ํ๋ค๊ณ ํ๋์ง ์๊ฐํด๋ณด์.
Preflight ์์ฒญ์์ ์ ์ ํ Access-Control ์ ํ์ธํ์ง ๋ชปํ๋ฉด ๋ฐ์ํ๊ฒ ๋๋ค!
์ด๋ฐ Preflight ์ํฉ์์ ์ ์ ํ Access-Control ์ ์ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด 3๊ฐ์ง๊ฐ ์กด์ฌํ๋ค.
CorsFilter
๋ก ์ง์ response์ header ๋ฅผ ๋ฃ์ด์ฃผ๊ธฐ- Controller ์์
@CrossOrigin
์ด๋ ธํ ์ด์ ์ถ๊ฐํ๊ธฐ WebMvcConfigurer
๋ฅผ ์ด์ฉํด์ ์ฒ๋ฆฌํ๊ธฐ
CorsFilter ์์ฑํ๊ธฐ
์ฒซ ๋ฒ์งธ๋ก ์์๋ณผ ๋ฐฉ๋ฒ์ ์ปค์คํ ํํฐ๋ฅผ ๋ง๋๋ ๊ฒ์ด๋ค.
Access-Control ์ ํ์ธํ ์ ์๋๋ก ์ปค์คํ
Filter ๋ฅผ ์์ฑํด๋ณด์.
ํ์๋ ์ด๋ฌํ ๊ตฌ์กฐ์ธ๋ฐ, ์๋ฌด ์์น์์๋ ์ข์ผ๋ filter ๋ผ๋ ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
๊ทธ๋ฆฌ๊ณ ํด๋น ๋๋ ํ ๋ฆฌ ์๋์ CorsFilter
๋ผ๋ ํด๋์ค๋ฅผ ํ๋ ์์ฑํด๋ณด๋๋ก ํ์.
๊ทธ๋ฆฌ๊ณ ํด๋น ํด๋์ค๋ฅผ Bean ์ผ๋ก ๋ฑ๋กํ๊ธฐ ์ํด์ ํ๋ ์์ํฌ์๊ฒ ์๋ ค์ผ ํ๋ค.
@Component
์ด๋ผ๋ ์ด๋
ธํ
์ด์
์ ์ถ๊ฐํ๊ณ , Filter
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ Override ํด๋ณด์.
์ฃผ์ํด์ผํ ๊ฒ์ด ์๋๋ฐ, Filter ๋ ๊ผญ javax.servlet ์ Filter๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
์ฐ๋ฆฌ๋ ํด๋น ํํฐ๊ฐ ์ค์ ๋ก ์ํํ doFilter
๋ฅผ ์ปค์คํ
ํด์ผ ํ๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ฑํด๋ณด์.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization");
if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
}else {
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
}
์ฐ๋ฆฌ๊ฐ ํ์ํ ํค๋๋ฅผ OPTIONS ๊ฐ ์ ํ์ธํ ์ ์๋๋ก ์ค์ ํด์ค ์ ์๋ค.
CrossOrigin ์ด๋ ธํ ์ด์ ์ฌ์ฉํ๊ธฐ
CrossOrigin ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋งค์ฐ ๊ฐ๋จํ๋ค.
์ปจํธ๋กค๋ฌ์์ ํน์ ๋ฉ์๋ ํน์ ์ปจํธ๋กค๋ฌ ์๋จ๋ถ์ @CrossOrigin
๋ง ์ถ๊ฐํ๋ฉด ๋๋ค.
@RestController
@RequestMapping(value = "/api/threats", produces = "application/json")
@CrossOrigin(origins = "http://front-server.com") // ์ปจํธ๋กค๋ฌ์์ ์ค์
public class ThreatController {
private final ThreatService threatService;
public ThreatController(ThreatService threatService) {
this.threatService = threatService;
}
@GetMapping
@CrossOrigin(origins = "http://front-server.com") // ๋ฉ์๋์์ ์ค์
public ResponseEntity<ThreatLogCountResponse> getAllThreatLogs() {
return ResponseEntity.ok(threatService.getAllThreatLogCount());
}
}
์ค์ ๋ฐฉ๋ฒ์ 2๊ฐ์ง๊ฐ ์๋๋ฐ,
- ์ปจํธ๋กค๋ฌ ํด๋์ค ๋จ์์ ์ค์
- ๋ฉ์๋ ๋จ์์ ์ค์
๋์ค ํ๋์ ๋ฐฉ๋ฒ๋ง ์ฌ์ฉํด๋ ๋๋ค.
ํ์ง๋ง ๋จ์ ์ด๋ผ๊ณ ํ๋ค๋ฉด ์ปจํธ๋กค๋ฌ๊ฐ ๋ง์ ์๋ก ์ค์ ํด์ผํ๋ ์ด๋ ธํ ์ด์ ์ด ๋ง์์ง๋ค๋ ๊ฒ์ด๋ค.
WebMvcConfigurer ์์ ์ค์ ํ๊ธฐ
ํด๋น ์ฌ์ฉ๋ฒ๋ ๊ฐ๋จํ๋ค.
Spring Initializer ๋ฅผ ์ด์ฉํด์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ main ํจ์๊ฐ ์กด์ฌํ๋ค.
@SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
}
ํด๋น main ํจ์์์ Bean ์ผ๋ก Configurer ๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค.
@SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://front-server.com");
}
};
}
}
๋ฌผ๋ก ์ด ๋ฐฉ๋ฒ์ @Configuration
์ ํ์ฉํ ํด๋์ค์์ ๋ฑ๋ก์ ํ ์๋ ์๋ค.
๋๊ธ