- User, Role, Auth 아래 참조.
CorsConfigurer
- cors 공격을 막기 위해 CorsConfigurer 를 구현하여 적용시켜야함. cors가 뭘까?
- cors
- http headers를 이용하여 요청을 제한하는것.
- 요청을 제한하지 않고 모두 허용한다면 copy 사이트를 만들어 사용자의 로그인을 유도 악용할 수 있음.
- Access-Control-Allow-Origin: 요청이 허용되는 출처를 나타내는 헤더
- Access-Control-Allow-Methods: 허용되는 HTTP 메서드를 지정하는 헤더
- Access-Control-Allow-Headers: 허용되는 HTTP 헤더를 지정하는 헤더
- Access-Control-Allow-Credentials: 인증된 요청의 허용 여부를 나타내는 헤더
- Access-Control-Expose-Headers: 브라우저에 노출할 헤더를 지정하는 헤더
구현 코드
- Security 6.x.x 버전으로 올라오면서 설정 방식이 많이 바뀌었음으로 버전 확인 필요
- Spring Boot Version : 3.2.2
- Java Version : 17
- Spring Security Version : 6.2.1
- CustomCorsConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class CustomCorsConfig implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("http://localhost:18231"));
corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
}
}
- SecurityFilterChain에 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Slf4j
@Configuration
@RequiredArgsConstructor
public class AngrySecurityConfiguration {
private final CustomCorsConfig customCorsConfig;
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.cors((cors) -> cors.configurationSource(customCorsConfig))
.authorizeHttpRequests(
(requests) -> requests.anyRequest().permitAll()
)
.formLogin(withDefaults())
.httpBasic(withDefaults());
return http.build();
}
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
UserDetails admin = org.springframework.security.core.userdetails.User.withUsername("admin").password("12345").roles("admin").build();
UserDetails user = org.springframework.security.core.userdetails.User.withUsername("user").password("12345").roles("user").build();
UserDetails guest = org.springframework.security.core.userdetails.User.withUsername("guest").password("12345").roles("guest").build();
return new InMemoryUserDetailsManager(admin, user, guest);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance(); // ToBE bcrypt
}
}
테스트 코드 작성
MockMvc
의 추가 설정을 통해 test 코드에서 security 테스트 진행 가능.security test 코드 기본 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootTest
public abstract class SecuritySetup {
@Autowired
private WebApplicationContext wac;
@Autowired
protected CustomCorsConfig customCorsConfig;
protected CorsConfiguration corsConfiguration;
protected MockHttpServletRequest httpServletRequest = new MockHttpServletRequest();
protected MockMvc mockMvc;
@BeforeEach
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.build();
corsConfiguration = customCorsConfig.getCorsConfiguration(httpServletRequest);
}
}
WebApplicationContext
: MockMvc 추가 설정을 위해 필요.CustomCorsConfig
: 내가 구성한 Cors 구성 정보를 로드하는데 필요.CorsConfiguration
: 내가 구성한 Cors 구성 정보를 로드하는데 필요.MockHttpServletRequest
: 내가 구성한 Cors 구성 정보를 로드하는데 필요.
- 왜 그런지는 모르겠으나
MockMvc
에서perform
메소드를 이용하여 cors 테스트를 할시
작동이 예상과 다르게됨. 찾아보니options
를 이용하여 테스트를 해야 한다고함.
그래서 header 에Access-Control-Request-Method
추가.Access-Control-Request-Method
: 브라우저가 서버로 보내는 사전 요청에서, 어떤 메서드를 사용할 것인지 서버에게 알리기 위해 사용
- 설정한 Cors 에서는
localhost:18231
에서 들어온 모든 요청을 요청 메소드 상관없이 허용 - 다른 호스트에서 요청할시 403 에러가 발생해야함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class SecurityCorsTest extends SecuritySetup {
@Test
void get_basic_get_pass() throws Exception {
mockMvc.perform(options("/basic")
.header("Origin", corsConfiguration.getAllowedOrigins())
.header("Access-Control-Request-Method", "GET")
)
.andExpect(status().isOk())
.andDo(print());
}
@Test
void get_basic_post_pass() throws Exception {
mockMvc.perform(options("/basic")
.header("Origin", corsConfiguration.getAllowedOrigins())
.header("Access-Control-Request-Method", "POST")
)
.andExpect(status().isOk())
.andDo(print());
}
@Test
void get_basic_get_fail() throws Exception {
mockMvc.perform(options("/basic")
.header("Origin", "https://www.google.com")
.header("Access-Control-Request-Method", "GET")
)
.andExpect(status().is4xxClientError())
.andDo(print());
}
@Test
void get_basic_post_fail() throws Exception {
mockMvc.perform(options("/basic")
.header("Origin", "https://www.google.com")
.header("Access-Control-Request-Method", "POST")
)
.andExpect(status().is4xxClientError())
.andDo(print());
}
}