1+ package com .back .global .security ;
2+
3+ import com .back .domain .user .entity .User ;
4+ import com .back .domain .user .repository .UserRepository ;
5+ import com .back .global .jwt .JwtUtil ;
6+ import com .fasterxml .jackson .databind .ObjectMapper ;
7+ import org .junit .jupiter .api .BeforeEach ;
8+ import org .junit .jupiter .api .DisplayName ;
9+ import org .junit .jupiter .api .Test ;
10+ import org .springframework .beans .factory .annotation .Autowired ;
11+ import org .springframework .boot .test .autoconfigure .web .servlet .AutoConfigureWebMvc ;
12+ import org .springframework .boot .test .context .SpringBootTest ;
13+ import org .springframework .http .MediaType ;
14+ import org .springframework .test .context .TestPropertySource ;
15+ import org .springframework .test .web .servlet .MockMvc ;
16+ import org .springframework .test .web .servlet .setup .MockMvcBuilders ;
17+ import org .springframework .transaction .annotation .Transactional ;
18+ import org .springframework .web .context .WebApplicationContext ;
19+
20+ import jakarta .servlet .http .Cookie ;
21+
22+ import static org .springframework .security .test .web .servlet .setup .SecurityMockMvcConfigurers .springSecurity ;
23+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .*;
24+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .*;
25+
26+ @ SpringBootTest
27+ @ AutoConfigureWebMvc
28+ @ TestPropertySource (locations = "classpath:application-test.yml" )
29+ @ Transactional
30+ class SecurityIntegrationTest {
31+
32+ @ Autowired
33+ private WebApplicationContext context ;
34+
35+ @ Autowired
36+ private UserRepository userRepository ;
37+
38+ @ Autowired
39+ private JwtUtil jwtUtil ;
40+
41+ @ Autowired
42+ private ObjectMapper objectMapper ;
43+
44+ private MockMvc mockMvc ;
45+ private User testUser ;
46+
47+ @ BeforeEach
48+ void setUp () {
49+ mockMvc = MockMvcBuilders
50+ .webAppContextSetup (context )
51+ .apply (springSecurity ())
52+ .build ();
53+
54+ testUser = User .builder ()
55+ .email ("test@example.com" )
56+ .nickname ("테스트사용자" )
57+ .oauthId ("test_123456" )
58+ .role ("USER" )
59+ .build ();
60+ testUser = userRepository .save (testUser );
61+ }
62+
63+ @ Test
64+ @ DisplayName ("JWT 토큰 없이 보호된 API 접근 - 통과 (현재 permitAll 설정)" )
65+ void accessProtectedApiWithoutToken () throws Exception {
66+ mockMvc .perform (get ("/api/test" )
67+ .contentType (MediaType .APPLICATION_JSON ))
68+ .andExpect (status ().isNotFound ()); // 404 (엔드포인트 없음) - 인증은 통과
69+ }
70+
71+ @ Test
72+ @ DisplayName ("유효한 JWT 토큰으로 API 접근 - CustomAuthenticationFilter 동작 확인" )
73+ void accessApiWithValidToken () throws Exception {
74+ // given - 실제 JWT 토큰 생성
75+ String accessToken = jwtUtil .generateAccessToken (testUser .getId (), testUser .getEmail (), testUser .getNickname ());
76+ Cookie tokenCookie = new Cookie ("accessToken" , accessToken );
77+
78+ // when & then - 실제 필터 체인 동작 확인
79+ mockMvc .perform (get ("/api/test" )
80+ .cookie (tokenCookie )
81+ .contentType (MediaType .APPLICATION_JSON ))
82+ .andExpect (status ().isNotFound ()); // 엔드포인트 없음이지만 인증은 성공
83+ }
84+
85+ @ Test
86+ @ DisplayName ("만료된 JWT 토큰으로 API 접근" )
87+ void accessApiWithExpiredToken () throws Exception {
88+ // given - 만료된 토큰 생성 (음수 만료시간)
89+ String expiredToken = jwtUtil .generateAccessTokenWithExpiration (
90+ testUser .getId (), testUser .getEmail (), testUser .getNickname (), -1000 );
91+ Cookie tokenCookie = new Cookie ("accessToken" , expiredToken );
92+
93+ // when & then
94+ mockMvc .perform (get ("/api/test" )
95+ .cookie (tokenCookie )
96+ .contentType (MediaType .APPLICATION_JSON ))
97+ .andExpect (status ().isNotFound ()); // 여전히 통과 (permitAll)
98+ }
99+
100+ @ Test
101+ @ DisplayName ("잘못된 형식의 JWT 토큰으로 API 접근" )
102+ void accessApiWithInvalidToken () throws Exception {
103+ // given
104+ Cookie invalidTokenCookie = new Cookie ("accessToken" , "invalid.jwt.token" );
105+
106+ // when & then
107+ mockMvc .perform (get ("/api/test" )
108+ .cookie (invalidTokenCookie )
109+ .contentType (MediaType .APPLICATION_JSON ))
110+ .andExpect (status ().isNotFound ()); // 여전히 통과 (permitAll)
111+ }
112+
113+ @ Test
114+ @ DisplayName ("OAuth2 로그인 엔드포인트 접근 - 실제 리다이렉트 확인" )
115+ void oAuth2LoginEndpoint () throws Exception {
116+ mockMvc .perform (get ("/oauth2/authorization/naver" ))
117+ .andExpect (status ().is3xxRedirection ())
118+ .andExpect (header ().exists ("Location" ))
119+ .andExpect (header ().string ("Location" ,
120+ org .hamcrest .Matchers .containsString ("nid.naver.com" )));
121+ }
122+
123+ @ Test
124+ @ DisplayName ("OAuth2 로그인 엔드포인트 - 카카오" )
125+ void kakaoLoginEndpoint () throws Exception {
126+ mockMvc .perform (get ("/oauth2/authorization/kakao" ))
127+ .andExpect (status ().is3xxRedirection ())
128+ .andExpect (header ().exists ("Location" ))
129+ .andExpect (header ().string ("Location" ,
130+ org .hamcrest .Matchers .containsString ("kauth.kakao.com" )));
131+ }
132+
133+ @ Test
134+ @ DisplayName ("OAuth2 로그인 엔드포인트 - 구글" )
135+ void googleLoginEndpoint () throws Exception {
136+ mockMvc .perform (get ("/oauth2/authorization/google" ))
137+ .andExpect (status ().is3xxRedirection ())
138+ .andExpect (header ().exists ("Location" ))
139+ .andExpect (header ().string ("Location" ,
140+ org .hamcrest .Matchers .containsString ("accounts.google.com" )));
141+ }
142+
143+ @ Test
144+ @ DisplayName ("CORS 헤더 확인" )
145+ void checkCorsHeaders () throws Exception {
146+ mockMvc .perform (options ("/api/test" )
147+ .header ("Origin" , "http://localhost:3000" )
148+ .header ("Access-Control-Request-Method" , "GET" ))
149+ .andExpect (status ().isOk ())
150+ .andExpect (header ().exists ("Access-Control-Allow-Origin" ))
151+ .andExpect (header ().exists ("Access-Control-Allow-Credentials" ));
152+ }
153+ }
0 commit comments