Skip to content
Merged
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- dev
- ci/*
- cd/*
- fix/test/*
workflow_dispatch:

jobs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,17 @@ protected void doFilterInternal(HttpServletRequest request,
throws ServletException, IOException {

String uri = request.getRequestURI();
log.info("🔍 [JWT 필터] 시작: {}", uri);
if (uri.equals("/") || uri.startsWith("/login") || uri.equals("/api/musicals") || uri.equals("/actuator/prometheus") || uri.equals("/api/user/me")) {

// 여기 경로는 Jwt Filter 무시
if (uri.equals("/") || uri.startsWith("/login") || uri.equals("/api/musicals") || uri.equals("/actuator/prometheus")) {
filterChain.doFilter(request, response); // 그냥 통과
/*if (uri.equals("/") || uri.startsWith("/login") || uri.equals("/api/musicals")) {
filterChain.doFilter(request, response);*/
return;
}

String accessToken = cookieUtil.getTokenFromCookie(request, "accessToken");
String refreshToken = cookieUtil.getTokenFromCookie(request, "refreshToken");

log.info("🔍 [JWT 필터] 요청 URI: {}", uri);
log.info("🔑 accessToken 존재 여부: {}", accessToken != null);
log.info("🔑 refreshToken 존재 여부: {}", refreshToken != null);

try {
if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

Expand All @@ -25,43 +28,65 @@ public class SecurityConfig {

@Value("${frontend.url}")
private String frontendUrl;
@Value("${backend.url}")
private String backendUrl;

private final String[] allowUrls = {
"/", "/favicon.ico", "/actuator/**", "/api/auth/**", "/error", "/oauth/**"
};

@Bean
public WebSecurityCustomizer configure() {
return web -> web.ignoring().requestMatchers(allowUrls);
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, OAuth2SuccessHandler oAuth2SuccessHandler, OAuth2FailureHandler oAuth2FailureHandler,
public SecurityFilterChain filterChain(HttpSecurity http,
OAuth2SuccessHandler oAuth2SuccessHandler,
OAuth2FailureHandler oAuth2FailureHandler,
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
log.info("[SecurityConfig]: 진입");
http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(frontendUrl)); // 프론트 주소
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
return config;
}))

http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())

.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/api/musicals", "/actuator/prometheus", "/api/user/me").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**", "/api/reservations/**", "/api/musicals/**").hasRole("USER")
.anyRequest().authenticated()
)

.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
.successHandler(oAuth2SuccessHandler)
.failureHandler(oAuth2FailureHandler)
)
.logout(logout -> logout.disable())

.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

/**
* ✅ Cors 설정 분리 - Credentials + Exposed Headers 등 명시
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of(
"Authorization",
"Content-Type",
"X-Requested-With",
"social_access_token"
));
configuration.setExposedHeaders(List.of(
"Authorization",
"Set-Cookie"
));
configuration.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@RequiredArgsConstructor
public class CookieUtil {

JwtTokenProvider jwtTokenProvider;
private final JwtTokenProvider jwtTokenProvider;

// ✅ 공통 쿠키 생성 유틸
public Cookie createCookie(String type, String token) {
Expand All @@ -30,7 +30,7 @@ public Cookie createCookie(String type, String token) {
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge((int) (expirationTime / 1000));
cookie.setDomain(".savemypodo.shop"); // 하위 도메인까지 허용
// cookie.setDomain(".savemypodo.shop"); // 하위 도메인까지 허용
cookie.setAttribute("SameSite", "None"); // CORS 허용

return cookie;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.bootcamp.savemypodo.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HealthController {
@GetMapping("/")
public ResponseEntity<String> health() {
return ResponseEntity.ok("OK");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.bootcamp.savemypodo.controller;

import com.bootcamp.savemypodo.config.security.utils.CookieUtil;
import com.bootcamp.savemypodo.dto.reservation.MyReservationResponse;
import com.bootcamp.savemypodo.dto.user.UserResponse;
import com.bootcamp.savemypodo.entity.User;
import com.bootcamp.savemypodo.global.exception.ErrorCode;
Expand All @@ -22,8 +21,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/user")
Expand Down Expand Up @@ -82,11 +79,4 @@ public ResponseEntity<UserResponse> getMyInfo(@AuthenticationPrincipal User user

return ResponseEntity.ok(userResponse);
}

@GetMapping("/my-reservations")
public ResponseEntity<List<MyReservationResponse>> getMyReservations(@AuthenticationPrincipal User user) {
List<MyReservationResponse> myReservations = reservationService.getMyReservationsByUser(user);
return ResponseEntity.ok(myReservations);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.bootcamp.savemypodo.service;

import com.bootcamp.savemypodo.dto.reservation.MyReservationResponse;
import com.bootcamp.savemypodo.entity.Musical;
import com.bootcamp.savemypodo.entity.Reservation;
import com.bootcamp.savemypodo.entity.Seat;
Expand All @@ -20,7 +19,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -90,12 +88,6 @@ public void doReservation(User user, Long mid, String seatName) {
redisSeatService.cacheSeatsForMusicalIfHot(mid);
}


public List<MyReservationResponse> getMyReservationsByUser(User user) {
List<Reservation> reservations = reservationRepository.findAllByUser(user);
return reservations.stream().map(MyReservationResponse::fromEntity).collect(Collectors.toList());
}

@Transactional
public void cancelReservation(Long userId, Long musicalId) {
// 1. 먼저 예약이 실제로 존재하는지 확인
Expand All @@ -122,5 +114,4 @@ public void cancelReservation(Long userId, Long musicalId) {
redisSeatService.cacheSeatsForMusicalIfHot(musicalId);

}

}
}
Loading