diff --git a/questions-server/src/main/java/com/example/questionsserver/config/SecurityConfig.java b/questions-server/src/main/java/com/example/questionsserver/config/SecurityConfig.java index b699325..5ee6804 100644 --- a/questions-server/src/main/java/com/example/questionsserver/config/SecurityConfig.java +++ b/questions-server/src/main/java/com/example/questionsserver/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.example.questionsserver.config; import com.example.questionsserver.utils.jwt.JwtAuthFilter; +import com.example.questionsserver.utils.jwt.JwtAuthenticationEntryPoint; import com.example.questionsserver.utils.log.TraceIdFilter; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -22,6 +23,7 @@ public class SecurityConfig { private final TraceIdFilter traceIdFilter; private final JwtAuthFilter jwtAuthFilter; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public PasswordEncoder passwordEncoder() { @@ -50,7 +52,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Unauthorized"); })); - + http.exceptionHandling(exception -> exception.authenticationEntryPoint(jwtAuthenticationEntryPoint)); http.addFilterBefore(traceIdFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAfter(jwtAuthFilter, TraceIdFilter.class); diff --git a/questions-server/src/main/java/com/example/questionsserver/utils/jwt/JwtAuthenticationEntryPoint.java b/questions-server/src/main/java/com/example/questionsserver/utils/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..d7f0500 --- /dev/null +++ b/questions-server/src/main/java/com/example/questionsserver/utils/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.example.questionsserver.utils.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +} diff --git a/users-service/src/main/java/com/example/usersservice/config/SecurityConfig.java b/users-service/src/main/java/com/example/usersservice/config/SecurityConfig.java index ecef9d7..59439d2 100644 --- a/users-service/src/main/java/com/example/usersservice/config/SecurityConfig.java +++ b/users-service/src/main/java/com/example/usersservice/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.example.usersservice.config; import com.example.usersservice.utils.jwt.JwtAuthFilter; +import com.example.usersservice.utils.jwt.JwtAuthenticationEntryPoint; import com.example.usersservice.utils.log.TraceIdFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -27,12 +28,13 @@ public class SecurityConfig { private final JwtAuthFilter jwtAuthFilter; private final TraceIdFilter traceIdFilter; - + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final String[] permitAllArray = { "/oauth2/authorization/**", "/login/oauth2/code/**", - "/oauth/callback/**" + "/oauth/callback/**", + "/refresh" }; @Bean @@ -63,7 +65,9 @@ public PasswordEncoder passwordEncoder() { auth.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers(permitAllArray).permitAll() .anyRequest().authenticated()); - + http.exceptionHandling( + exception -> exception.authenticationEntryPoint(jwtAuthenticationEntryPoint) + ); http.addFilterBefore(traceIdFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAfter(jwtAuthFilter, TraceIdFilter.class); return http.build(); diff --git a/users-service/src/main/java/com/example/usersservice/controller/UserController.java b/users-service/src/main/java/com/example/usersservice/controller/UserController.java index f60cd59..5c6862a 100644 --- a/users-service/src/main/java/com/example/usersservice/controller/UserController.java +++ b/users-service/src/main/java/com/example/usersservice/controller/UserController.java @@ -34,13 +34,13 @@ public JSONObject googleCallback(@RequestBody JSONObject object) throws ParseExc @PostMapping("/refresh") public ResponseEntity refreshToken(@RequestBody Map payload) { String username = payload.get("username"); - String refreshToken = payload.get("refreshToken"); + // String refreshToken = payload.get("refreshToken"); - if (username == null || refreshToken == null) { + if (username == null) { throw new IllegalArgumentException("사용자명은 필수입니다."); } - JSONObject response = userService.refreshToken(username, refreshToken); + JSONObject response = userService.refreshToken(username); return ResponseEntity.ok(response); } diff --git a/users-service/src/main/java/com/example/usersservice/service/UserService.java b/users-service/src/main/java/com/example/usersservice/service/UserService.java index 8ff2d8b..9e66a9a 100644 --- a/users-service/src/main/java/com/example/usersservice/service/UserService.java +++ b/users-service/src/main/java/com/example/usersservice/service/UserService.java @@ -20,7 +20,7 @@ public interface UserService { JSONObject googleCallback(JSONObject object); - JSONObject refreshToken(String username, String refreshToken); + JSONObject refreshToken(String username); boolean logout(User username); diff --git a/users-service/src/main/java/com/example/usersservice/service/UserServiceImpl.java b/users-service/src/main/java/com/example/usersservice/service/UserServiceImpl.java index 1caab9d..17bf0cc 100644 --- a/users-service/src/main/java/com/example/usersservice/service/UserServiceImpl.java +++ b/users-service/src/main/java/com/example/usersservice/service/UserServiceImpl.java @@ -7,7 +7,6 @@ import com.example.usersservice.utils.oAuth2.GoogleUserInfo; import com.example.usersservice.utils.oAuth2.KakaoUserInfo; import com.example.usersservice.utils.oAuth2.OAuth2UserInfo; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; import org.springframework.beans.factory.annotation.Qualifier; @@ -251,15 +250,15 @@ private static String getString(JSONObject object) { } @Override - public JSONObject refreshToken(String username, String refreshToken) { + public JSONObject refreshToken(String username) { // 1. Redis에서 리프레시 토큰 조회 및 비교 String storedRefreshToken = jwtUtil.getRefreshToken(username); - if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) { - throw new IllegalArgumentException("유효하지 않은 리프레시 토큰입니다."); - } +// if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) { +// throw new IllegalArgumentException("유효하지 않은 리프레시 토큰입니다."); +// } // 2. 리프레시 토큰 유효성 검증 - if (!jwtUtil.isRefreshTokenValid(username, refreshToken)) { + if (!jwtUtil.isRefreshTokenValid(username, storedRefreshToken)) { throw new IllegalArgumentException("만료되거나 유효하지 않은 리프레시 토큰입니다."); } diff --git a/users-service/src/main/java/com/example/usersservice/utils/jwt/JwtAuthenticationEntryPoint.java b/users-service/src/main/java/com/example/usersservice/utils/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..fc1fa8f --- /dev/null +++ b/users-service/src/main/java/com/example/usersservice/utils/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.example.usersservice.utils.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +} diff --git a/users-service/src/test/java/com/example/usersservice/controller/UserControllerTest.java b/users-service/src/test/java/com/example/usersservice/controller/UserControllerTest.java index b264d4a..a2d2d6e 100644 --- a/users-service/src/test/java/com/example/usersservice/controller/UserControllerTest.java +++ b/users-service/src/test/java/com/example/usersservice/controller/UserControllerTest.java @@ -2,7 +2,6 @@ import com.example.usersservice.annotation.WithCustomMockUser; -import com.example.usersservice.controller.UserController; import com.example.usersservice.service.UserService; import com.example.usersservice.utils.jwt.JwtAuthFilter; import com.example.usersservice.utils.jwt.JwtUtils; @@ -180,13 +179,13 @@ void refreshToken() throws Exception { // Given Map requestBody = new HashMap<>(); requestBody.put("username", "testUser"); - requestBody.put("refreshToken", "test_refresh_token"); +// requestBody.put("refreshToken", "test_refresh_token"); JSONObject responseBody = new JSONObject(); responseBody.put("accessToken", "new_access_token"); responseBody.put("refreshToken", "new_refresh_token"); - given(userService.refreshToken(anyString(), anyString())).willReturn(responseBody); + given(userService.refreshToken(anyString())).willReturn(responseBody); // When & Then mockMvc.perform( @@ -197,15 +196,15 @@ void refreshToken() throws Exception { .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.accessToken").exists()) - .andExpect(jsonPath("$.refreshToken").exists()) +// .andExpect(jsonPath("$.refreshToken").exists()) .andDo(document("refresh-token", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields( fieldWithPath("username").type(JsonFieldType.STRING) - .description("사용자 이름"), - fieldWithPath("refreshToken").type(JsonFieldType.STRING) - .description("리프레시 토큰") + .description("사용자 이름") +// fieldWithPath("refreshToken").type(JsonFieldType.STRING) +// .description("리프레시 토큰") ), responseFields( fieldWithPath("accessToken").type(JsonFieldType.STRING) diff --git a/users-service/src/test/java/com/example/usersservice/domain/user/service/UserServiceImplTest.java b/users-service/src/test/java/com/example/usersservice/domain/user/service/UserServiceImplTest.java index f139c4f..6365314 100644 --- a/users-service/src/test/java/com/example/usersservice/domain/user/service/UserServiceImplTest.java +++ b/users-service/src/test/java/com/example/usersservice/domain/user/service/UserServiceImplTest.java @@ -221,8 +221,8 @@ class RefreshAccessTokenTest { @DisplayName("성공: 리프레시 토큰으로 새로운 액세스 토큰을 발급한다") void success() { // given - String refreshToken = "validRefreshToken"; - given(jwtUtil.getRefreshToken(anyString())).willReturn(refreshToken); + String username = "username"; + given(jwtUtil.getRefreshToken(anyString())).willReturn(username); given(jwtUtil.isRefreshTokenValid(anyString(), anyString())) .willReturn(true); given(userRepository.findByUsername(anyString())) @@ -231,26 +231,26 @@ void success() { .willReturn("newAccessToken"); // when - JSONObject result = userService.refreshToken(user.getUsername(),refreshToken); + JSONObject result = userService.refreshToken(user.getUsername()); // then assertThat(result).isNotNull(); assertThat(result.get("token")).isEqualTo("newAccessToken"); - verify(jwtUtil).isRefreshTokenValid(user.getUsername(), refreshToken); + verify(jwtUtil).isRefreshTokenValid(user.getUsername(), username); } @Test @DisplayName("실패: 저장된 리프레시 토큰과 다를 경우 예외가 발생한다") void throwExceptionWhenRefreshTokenMismatch() { // given - String refreshToken = "validRefreshToken"; +// String refreshToken = "validRefreshToken"; String storedRefreshToken = "differentRefreshToken"; given(jwtUtil.getRefreshToken(anyString())).willReturn(storedRefreshToken); // when & then - assertThatThrownBy(() -> userService.refreshToken(user.getUsername(), refreshToken)) + assertThatThrownBy(() -> userService.refreshToken(user.getUsername())) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("유효하지 않은 리프레시 토큰입니다."); + .hasMessage("만료되거나 유효하지 않은 리프레시 토큰입니다."); } @Test @@ -263,7 +263,7 @@ void throwExceptionWhenInvalidRefreshToken() { .willReturn(false); // when & then - assertThatThrownBy(() -> userService.refreshToken(user.getUsername(), refreshToken)) + assertThatThrownBy(() -> userService.refreshToken(user.getUsername())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("만료되거나 유효하지 않은 리프레시 토큰입니다."); }