근데 재발급 로직이 안됨..
React 에서 요청을 보냈을 때, 아예 Controller 의 메서드로 들어가지도 못한다..
일단 Postman 으로 로그인을 했을 때, refresh Token 을 Cookie 로 받음 (이 쿠키는 Http only 상태임)
다시 재발급 받는 요청을 보냈을 때, 정상적으로 새로운 Access Token 이 리턴됨
다시 React 로 돌아와서,
로그인을 시도했을 때, 응답 헤더의 Set-Cookie를 보면 잘 들어오는데..
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/member")
public class MemberController {
...
@GetMapping("/reissue")
public ResponseEntity<TokenDto> reissue(@CookieValue("refreshToken") String refreshToken) {
log.info("재발급 로직 입장");
log.info("refreshToken 정보 = {}", refreshToken);
String newAccessToken = authService.reissueAccessToken(refreshToken);
log.info("AccessToken 재발급 = {}", newAccessToken);
return ResponseEntity.status(HttpStatus.OK).body(new TokenDto(newAccessToken, ""));
}
근데 토큰을 재발급받는 메서드에 아예 접근이 안됨
혹시
@CookieValue("refreshToken") String refreshToken
이 부분 때문인가 싶어서
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/member")
public class MemberController {
...
@GetMapping("/reissue")
public ResponseEntity<TokenDto> reissue() {
log.info("재발급 로직 입장");
return ResponseEntity.status(HttpStatus.OK).body(new TokenDto(null, ""));
}
아예 파라미터로 아무것도 받지 않게 코드를 변경함
변경하고 다시 재발급 요청을 보내니까,
요청이 메서드에 정상적으로 접근함..
이걸 보고 든 생각이, React 에서도 Http Only 인 Cookie 지만 따로 설정해줘야 하는 것이 있나..? 라는 생각을 함
→ 맞았다.. 물론 Spring 코드도 좀 변경했음!
// Axios 인스턴스 생성
const interceptor = axios.create({
baseURL: 'http://localhost:8080/api/v1',
withCredentials: true,
headers: {
'Access-Control-Allow-Origin': 'http://localhost:8080',
}
});
이건 React 인터셉터 생성 코드인데,
withCredentials: true,
이 부분을 추가해줘야 백엔드에 요청보낼 때, 토큰도 보낼 수 있다고 한다..
그리고
@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable);
http.exceptionHandling(e -> e.authenticationEntryPoint(jwtAuthenticationEntryPoint).accessDeniedHandler(jwtAccessDeniedHandler));
// 권한 규칙 설정
http.cors((cors) -> cors
.configurationSource(corsConfigurationSource()))
.logout((logoutConfig) -> {
logoutConfig.logoutUrl("/api/v1/member/logout")
.addLogoutHandler((request, response, authentication) -> {
HttpSession session = request.getSession();
session.invalidate();
}).deleteCookies("refreshToken");
logoutConfig.logoutSuccessHandler((request, response, authentication) -> {
response.setStatus(HttpStatus.OK.value());
response.getWriter().write("logout");
response.getWriter().flush();
});
})
.authorizeHttpRequests(
authorize -> authorize
.requestMatchers("/api/v1/member/login").permitAll()
.requestMatchers("/api/v1/member/join").permitAll()
.requestMatchers("/api/v1/member/reissue").permitAll()
.requestMatchers("/api/v1/member/find-id").permitAll()
.requestMatchers("/api/v1/member/find-password").permitAll()
.anyRequest().authenticated()
).apply(new JwtSecurityConfig(jwtTokenProvider));
return http.build();
}
// CORS 허용 적용
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.addExposedHeader("Set-Cookie"); // Set-Cookie 라는 헤더 노출
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
CORS 설정 코드에 Set-Cookie
라는 헤더를 노출하는 설정을 추가함
다 하고, 다시 요청을 보내니까
쿠키 저장 잘 되고, Http Only 설정도 잘 되고..
백엔드에 요청도 잘 감
React 인터셉터 코드
import axios from 'axios';
import { setCookie, getCookie, removeCookie } from "../util/Cookie";
import { useNavigate } from "react-router-dom";
// Axios 인스턴스 생성
const interceptor = axios.create({
baseURL: 'http://localhost:8080/api/v1',
withCredentials: true,
headers: {
'Access-Control-Allow-Origin': 'http://localhost:8080',
}
});
// 요청 인터셉터 추가
interceptor.interceptors.request.use(
(config) => {
if (config.url !== '/member/login' && config.url !== '/member/join') {
const accessToken = getCookie('accessToken');
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => {
console.log(error);
}
);
// 응답 인터셉터 추가
interceptor.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originRequest = error?.config;
if (error.response.status === 401) {
try {
const response = await interceptor.get('/member/reissue');
if (response.data.accessToken) {
removeCookie('accessToken');
setCookie('accessToken', response.data.accessToken);
// 업데이트된 액세스 토큰을 사용하여 Authorization 헤더 업데이트
originRequest.headers["Authorization"] = "Bearer " + response.data.accessToken;
// 재요청
return interceptor(originRequest);
}
} catch (error) {
return Promise.reject(error);
}
}
},
);
export default interceptor;