vue์ spring์
์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์ ๊ฐ์ ธ ์๊ธฐ๋
CORS ์๋ฌ๋ฅผ ํด๊ฒฐํด๋ณด์.
1. ๋ฌธ์ ์ดํผ๊ธฐ
ํ์ฌ ํ๋ก์ ํธ๋ Vue๋ก ํ๋ก ํธ๋ฅผ ๋งก๊ณ ์๊ณ , Spring์ผ๋ก ๋ฐฑ์๋๋ฅผ ๋งก๊ณ ์๋ค. Vue์์ ํ์๊ฐ์ ์ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ์ ๋ ฅํ ํ ๋ฒํผ์ ๋๋ฌ๋ Spring์ ์ด๋ค ๋ฐ์ดํฐ๋ ๋์ฐฉํ์ง ์์๋ค. ์ ๊ทธ๋ฐ๊ฑธ๊น?
Vue๋ http://localhost:5173/๋ฅผ ๊ธฐ๋ณธ ์ฃผ์๋ก ์ฌ์ฉํ๊ณ Spring์ http://localhost:8080/์ ๊ธฐ๋ณธ ์ฃผ์๋ก ์ฌ์ฉํ๋ค. Spring์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์์ ์จ ์์ฒญ์ ๊ฑฐ๋ถํ๊ธฐ ๋๋ฌธ์, Vue์์ ์จ ์์ฒญ์ด ์ ๋๋ก ์ฒ๋ฆฌ๋์ง ์๋๋ค. ์ ๋๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด Spring ๋ด๋ถ์ ์ค์ ์ ์ถ๊ฐํด์ผํ๋ค.
2. ์ฝ๋ ์ดํผ๊ธฐ
- @CrossOrigin + http.cors( ) ์ฝ๋ ์์
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:5173")
public class RegisterController {
@Autowired
private MemberService memberService;
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody RegisterRequest request) {
....
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> {});
return http.build();
}
}
@CrossOrigin์ origins์ ์ ์ ๊ฒฝ๋ก๋ก ์์ฒญ์ด ์ฌ ๊ฒฝ์ฐ ํด๋น ์ปจํธ๋กค๋ฌ๊ฐ ์๋ํ๋๋ก ์ฐ๊ฒฐํ๋ค. ๊ทธ๋ฌ๋ @CrossOrigin๋ง ์ค์ ํ๋ค๊ณ ํด๊ฒฐ๋๋ ๊ฒ์ ์๋๋ค.
๋ธ๋ผ์ฐ์ ๋ ๋ณธ์์ฒญ ์ ์ ํ๋ฆฌํ๋ผ์ดํธ(Preflight)๋ฅผ ๋ณด๋ธ๋ค. ์ด ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ค์ ์์ฒญ ์ ์ ์๋ฒ์ ๋ณด๋ด๋ ์ฌ์ ๊ฒ์ฌ ์์ฒญ์ด๋ค. ์ด ์์ฒญ์ ํตํด ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ๊ฐ ํด๋น ์์ฒญ์ ํ์ฉํ๋์ง ํ์ธํ๋ค. ์ด๋ ํ๋ฆฌํ๋ผ์ดํ ์์ฒญ์ OPTIONS ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์๋ฒ๋ก ๋ณด๋ด์ง๋ฉฐ, ์๋ฒ๋ ์ด ์์ฒญ์ ๋ํ ์๋ต์ผ๋ก CORS ํค๋๋ฅผ ์ค์ ํ์ฌ ํด๋ผ์ด์ธํธ๊ฐ ์ค์ ์์ฒญ์ ํ ์ ์๋์ง ์๋ ค์ค๋ค.
ํ๋ฆฌํ๋ผ์ดํธ๋ ์คํ๋ง ์ํ๋ฆฌํฐ ํํฐ๋ฅผ ๋จผ์ ์ง๋๊ฐ๋ฉด์ http.cors( )๊ฐ ๊บผ์ ธ ์์ผ๋ฉด, ์ํ๋ฆฌํฐ๊ฐ ํ๋ฆฌํ๋ผ์ดํธ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํด 401 ํน์ 403์ด ๋๊ณ @CrossOrigin์ด ์๋ ๊ณณ๊น์ง ๋๋ฌํ์ง ๋ชปํ๋ค. ๊ทธ๋์ SpringSecurity ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ด๋ ธํ ์ด์ ์ฃผ์ ํ๊ณ SecurityFilterChain์์ http.cors(cors -> {});๋ฅผ ์ ์ด ํต๊ณผ๋๊ฒ ํด์ค๋ค.
๋ฌผ๋ก ์ด ๋ฐฉ๋ฒ๋ง๊ณ ๋ ๊ถ์ฅํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ๋ฐ๋ก CORS๋ฅผ ์ ์ญ ์ค์ ํ๋ ๊ฒ์ด๋ค. @CrossOrigin์ด ์ปจํธ๋กค๋ฌ๋ง๋ค ๋ถ์ด์์ด์ ๊ด๋ฆฌ๋น์ฉ์ด ์ฌ๋ผ๊ฐ๊ณ ์ถฉ๋์ด ์ผ์ด๋ ์๋ ์๋ค. ์ด๋ฐ ์ ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ ์ญ ๊ด๋ฆฌ๋ฅผ ํด์ฃผ๋๊ฒ ๋ ์ข๋ค. (๊ทผ๋ฐ ๋ ์ํ๋ค)
- ์ ์ญ ์ค์ ์ฝ๋ ์์
@Configuration
public class SecurityConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
๋งคํ์ ๋ชจ๋ ๊ฒฝ๋ก๊ฐ ๊ฐ๋ฅํ๋๋ก /**๋ก ์ค์ ํ๊ณ , ๋๋ฉ์ธ์ http://localhost:5173์ด ๋๋ค. ๊ทธ๋ฌ๋ฉด localhost:5173์ผ๋ก ๋ถํฐ ์จ ๋ชจ๋ ๊ฒฝ๋ก์ธ http://localhost:5173/login ํน์ http://localhost:5173/register ๋ฑ ๋ค ๊ฐ๋ฅํ๋๋ก ์ค์ ํ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ฒ ์์ฒญ ๋ฐฉ์์ "GET", "POST", "PUT", "DELETE"๋ง ๊ฐ๋ฅํ๋๋ก ํ๋ค.
.allowedHeaders("*")๋ ๋ชจ๋ ํค๋๋ฅผ ํ์ฉํ๋ค. ์ฌ๊ธฐ์ ํค๋๋ Host(์์ฒญ ์๋ฒ ์ฃผ์), Origin(์์ฒญ์ ๋ณด๋ธ ๋๋ฉ์ธ), Authorization(JWT ํ ํฐ ๊ฐ์ ์ธ์ฆ ์ ๋ณด), Content-Type(ํ ์คํธ ํน์ Json ๋ฑ), ์ฟ ํค์ ๊ฐ์ HTTP ์์ฒญ/์๋ต์ ๋ถ๋ ๋ถ๊ฐ ์ ๋ณด๋ฅผ ๋งํ๋ค. ๋ธ๋ผ์ฐ์ ๋ ์ด ์์ฒญ์ ์ด๋ฐ ํค๋๋ค์ ๋ถ์ฌ๋ ๋๋์ง ์๋ฒ์๊ฒ ๋ฌป๋๋ฐ, *์ ํตํด ๋ชจ๋ ํค๋๋ฅผ ์ฌ์ฉ ๊ฐ๋ฅํ๋๋ก ์ค์ ํด๋ ๊ฒ์ด๋ค. ๋ด๊ฐ ๋ณด๋ผ ์์ฒญ์ ํ์ํ ํค๋๋ค์ ๋ฐ์ ์ ์๋๋ก ์ค์ ํด๋ฌ์ผ CORS ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
.allowCredentials(true)์ ์๊ฒฉ ์ฆ๋ช ์ ๊ดํ ๊ฒ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ์ ๋ณด๋ผ ๋ ํจ๊ป ์ ๋ฌํ ์ ์๋ ๋ฏผ๊ฐํ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋งํ๋ค. ๋ณดํต ์๊ฒฉ์ฆ๋ช ์ ์ฟ ํค, Authorization ํค๋ ๋ฑ์ด ์๋ค. ํ๋ก ํธ์์ ๋ฐฑ์๋๋ก ์์ฒญ์ ๋ณด๋ผ ๋ ์ฟ ํค๋ ์ธ์ฆํค๋๋ฅผ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ๋ถ์ผ ์ ์๋์ง ์ค์ ํ๋ค. true๋ ๋น์ฐํ ํฌํจํด๋ ์ข๋ค๋ผ๋ ์๋ฏธ์ด๋ค.
Spring Boot์์ CORS ์ค์ ํ๊ธฐ: ํฌ๋ก์ค ๋๋ฉ์ธ ์์ฒญ ํ์ฉ์ ์ํ ํ์ ๊ฐ์ด๋
์น ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์์ ํต์ ํด์ผ ํ๋ ์ํฉ์ด ์์ฃผ ๋ฐ์ํฉ๋๋ค. ์ด๋ ์ค์ํ ๊ฐ๋ ์ค ํ๋๊ฐ **CORS(Cross-Origin Resource Sharing)**์ ๋๋ค. ์ด ๊ธ์์๋ CORS์
digitalbourgeois.tistory.com
3. ์ค์ ์ฝ๋ ์์
//์์กด์ฑ์ฃผ์
@Configuration
//์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ํ์ฑํํ๊ณ ์น ๋ณด์ ์ค์ ์ ๊ตฌ์ฑ
@EnableWebSecurity
public class SecurityConfig {
//๋น๋ฑ๋ก
@Bean
//์ค์ ๋ณด์ ๊ท์น(์ธ๊ฐ/์ธ์ฆ/CSRF/CORS ๋ฑ)์ ์ ์ํ ๋น
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
// ์ค๋ฅ๋ก ์ธํด ํ์ฉํด์ค -> ๊ฐ๋ฐ ์ดํ ๋ค์ ์์ ํด์ผํจ
.authorizeHttpRequests(authz -> authz
//ํด๋น ๊ฒฝ๋ก๋ ๋๊ตฌ๋ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก
.requestMatchers("/api/register").permitAll()
.requestMatchers("/api/login").permitAll()
//DB๋ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก
.requestMatchers("/h2-console/**").permitAll()
//Kakao ๋ก๊ทธ์ธ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก
.requestMatchers("/oauth/kakao").permitAll()
// ๋ชจ๋ ์์ฒญ ์ธ์ฆ ์์ด ํ์ฉ
.anyRequest().permitAll()
)
//ํค๋ ์ค์ , H2(DB) ์ฝ์์ iframe์ผ๋ก ๋์ฐ๊ธฐ ์ํด ์ค์
.headers(headers
-> headers.frameOptions(frameOptions -> frameOptions.disable()))
//CORS ํ์ฉํ๊ฒ ๋ค๊ณ ์ ์
.cors(cors -> {});
//์์ ์ค์ ๊ฐ์ ํ ๋๋ก SecurityFilterChain ๊ฐ์ฒด๋ฅผ ์์ฑํด์ ๋น์ผ๋ก ๋ฑ๋ก
return http.build();
}
// ๋น๋ฐ๋ฒํธ ํด์ ๋น, ํ์๊ฐ์
์ ํด์ ์ ์ฅํ๊ณ ๋ก๊ทธ์ธ ์ ํด์ ๋น๊ต์ ํ์
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- @EnableWebSercurity์ SecurityFilterChain
[SpringSecurity] @EnableWebSecurity, SecurityFilterChain ๊ฐ๋ ๊ณผ ์์
@EnableWebSecurity๋์ธ์ ์ฐ๋๊ฑธ๊น? @EnableWebSecurity์ด๋ ธํ ์ด์ ์ ๋ฑ๋กํ์ฌ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ํ์ฑํํ๊ณ ์น ๋ณด์ ์ค์ ์ ๊ตฌ์ฑํ๋๋ฐ ์ฌ์ฉํ๋ค.์ฆ, ๋ฑ๋กํ๋ฉด ๋ณด์๊ณผ ๊ด๋ จ๋ ๋น์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค
post-this.tistory.com