
SecurityConfig ํด๋์ค์ CorsConfigurationSource๋ฅผ ๋ฑ๋กํฉ๋๋ค.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// ํ์ฉํ Origin (ํน์ ๋๋ฉ์ธ๋ง ํ์ฉ ๊ฐ๋ฅ)
config.setAllowedOrigins(Arrays.asList("http://localhost:5173","http://localhost:3000"));
// ๋ชจ๋ HTTP ๋ฉ์๋ ํ์ฉ
config.setAllowedMethods(Arrays.asList("*"));
// ๋ชจ๋ Header ํ์ฉ
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(false);
config.applyPermitDefaultValues();
source.registerCorsConfiguration("/**", config);
return source;
}
โ
*๋ก ์ค์ ํ๋ฉด ๋ชจ๋ Origin์ ํ์ฉํ์ง๋ง, ์ค์ ๋ฐฐํฌ ํ๊ฒฝ์์๋ ํน์ ๋๋ฉ์ธ๋ง ๋ช ์ํ๋ ๊ฒ์ด ์์ ํฉ๋๋ค.
HttpSecurity ์ค์ ์์ .cors(withDefaults())๋ฅผ ์ถ๊ฐํฉ๋๋ค.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.cors(withDefaults()) // โ
cors ์ค์ ์ถ๊ฐ
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(exceptionHandler));
return http.build();
}
role ํ๋๋ฅผ ๊ฐ์ง๋๋ค. ("USER", "ADMIN")hasRole() ๋ฉ์๋๋ก ํน์ ์๋ํฌ์ธํธ ์ ๊ทผ์ ์ ํํ ์ ์์ต๋๋ค.@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.cors(withDefaults())
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // ๊ด๋ฆฌ์ ์ ์ฉ
.requestMatchers("/user/**").hasRole("USER") // ์ผ๋ฐ ์ฌ์ฉ์ ์ ์ฉ
.anyRequest().authenticated())
.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(exceptionHandler));
return http.build();
}
โ ๋ณดํต์
USER < MANAGER < ADMINํํ์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ์ค๊ณํฉ๋๋ค.
ํด๋์ค๋ ๋ฉ์๋ ์์ ์ ๋ํ ์ด์ ์ ๋ถ์ฌ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์๋ ์์ต๋๋ค.
@PreAuthorize("hasRole('ADMIN')")@Secured("ROLE_USER")โ ๏ธ ๊ธฐ๋ณธ ์ค์ ์์๋ ๋นํ์ฑํ๋์ด ์์ผ๋ฏ๋ก @EnableMethodSecurity๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
Spring Boot์์๋ spring-boot-starter-test๋ก ํ
์คํธ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
(JUnit, Mockito, AssertJ ๊ธฐ๋ณธ ํฌํจ)
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'com.h2database:h2'
@DataJpaTest
class OwnerRepositoryTest {
@Autowired
private OwnerRepository ownerRepository;
@Test
@DisplayName("์ญ์ ํ
์คํธ")
void deleteOwners() {
ownerRepository.save(new Owner("ํ๋ฐฑ", "๋ฐ"));
ownerRepository.deleteAll();
assertThat(ownerRepository.count()).isEqualTo(0);
}
}
์ค์ ์๋ฒ๋ฅผ ๋์ฐ์ง ์๊ณ HTTP ์์ฒญ์ ์๋ฎฌ๋ ์ด์ .
@SpringBootTest
@AutoConfigureMockMvc
public class CarRestTest {
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("๋ก๊ทธ์ธ ์ธ์ฆ ํ
์คํธ")
public void testAuthentication() throws Exception {
this.mockMvc.perform(post("/login")
.content("{\"username\":\"admin\",\"password\":\"admin\"}")
.header(HttpHeaders.CONTENT_TYPE, "application/json"))
.andDo(print())
.andExpect(status().isOk());
}
}
@DataJpaTest
public class CarRepositoryTest {
@Autowired
private CarRepository carRepository;
@Autowired
private OwnerRepository ownerRepository;
@Test
@DisplayName("์ฐจ๋ ์ ์ฅ ํ
์คํธ")
void saveCar() {
Owner owner = new Owner("Gemini", "GPT");
ownerRepository.save(owner);
Car car = new Car("Ford", "Mustang", "Red", "ABCEDF", 2021, 567890, owner);
carRepository.save(car);
assertThat(carRepository.findById(car.getId())).isPresent();
assertThat(carRepository.findById(car.getId()).get().getBrand()).isEqualTo("Ford");
}
}
USER, ADMIN ์ญํ ์ ๋ฐ๋ผ ์ ๊ทผ ๊ถํ ๋ถ๋ฆฌ