Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,6 +23,7 @@ public class SecurityConfig {

private final TraceIdFilter traceIdFilter;
private final JwtAuthFilter jwtAuthFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Bean
public PasswordEncoder passwordEncoder() {
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ public JSONObject googleCallback(@RequestBody JSONObject object) throws ParseExc
@PostMapping("/refresh")
public ResponseEntity<JSONObject> refreshToken(@RequestBody Map<String, String> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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("만료되거나 유효하지 않은 리프레시 토큰입니다.");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -180,13 +179,13 @@ void refreshToken() throws Exception {
// Given
Map<String, String> 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(
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand All @@ -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
Expand All @@ -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("만료되거나 유효하지 않은 리프레시 토큰입니다.");
}
Expand Down
Loading