Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
b25c2edcc0
1
.github/workflows/member-ci.yml
vendored
1
.github/workflows/member-ci.yml
vendored
@ -23,6 +23,7 @@ env:
|
|||||||
MANIFEST_REPO: dg04-hi/hi-manifest
|
MANIFEST_REPO: dg04-hi/hi-manifest
|
||||||
MANIFEST_FILE_PATH: member/deployment.yml
|
MANIFEST_FILE_PATH: member/deployment.yml
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -119,32 +120,283 @@ public class AnalyticsService implements AnalyticsUseCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StoreStatisticsResponse getStoreStatistics(Long storeId, LocalDate startDate, LocalDate endDate) {
|
public StoreStatisticsResponse getStoreStatistics(Long storeId, LocalDate startDate, LocalDate endDate) {
|
||||||
// 이전 구현과 동일
|
log.info("매장 통계 조회 시작: storeId={}, startDate={}, endDate={}", storeId, startDate, endDate);
|
||||||
return null; // 구현 생략
|
|
||||||
|
try {
|
||||||
|
// 1. 캐시 키 생성
|
||||||
|
String cacheKey = String.format("statistics:store:%d:%s:%s", storeId, startDate, endDate);
|
||||||
|
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||||
|
if (cachedResult.isPresent()) {
|
||||||
|
log.info("캐시에서 통계 데이터 반환: storeId={}", storeId);
|
||||||
|
return (StoreStatisticsResponse) cachedResult.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 주문 통계 데이터 조회 (실제 OrderStatistics 도메인 필드 사용)
|
||||||
|
var orderStatistics = orderDataPort.getOrderStatistics(storeId, startDate, endDate);
|
||||||
|
|
||||||
|
// 3. 응답 생성
|
||||||
|
StoreStatisticsResponse response = StoreStatisticsResponse.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.startDate(startDate)
|
||||||
|
.endDate(endDate)
|
||||||
|
.totalOrders(orderStatistics.getTotalOrders())
|
||||||
|
.totalRevenue(orderStatistics.getTotalRevenue())
|
||||||
|
.averageOrderValue(orderStatistics.getAverageOrderValue())
|
||||||
|
.peakHour(orderStatistics.getPeakHour())
|
||||||
|
.popularMenus(orderStatistics.getPopularMenus())
|
||||||
|
.customerAgeDistribution(orderStatistics.getCustomerAgeDistribution())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 캐시에 저장
|
||||||
|
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofMinutes(30));
|
||||||
|
|
||||||
|
log.info("매장 통계 조회 완료: storeId={}", storeId);
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("매장 통계 조회 중 오류 발생: storeId={}", storeId, e);
|
||||||
|
throw new RuntimeException("매장 통계 조회에 실패했습니다.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AiFeedbackSummaryResponse getAIFeedbackSummary(Long storeId) {
|
public AiFeedbackSummaryResponse getAIFeedbackSummary(Long storeId) {
|
||||||
// 이전 구현과 동일
|
log.info("AI 피드백 요약 조회 시작: storeId={}", storeId);
|
||||||
return null; // 구현 생략
|
|
||||||
|
try {
|
||||||
|
// 1. 캐시에서 확인
|
||||||
|
String cacheKey = "ai_feedback_summary:store:" + storeId;
|
||||||
|
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||||
|
if (cachedResult.isPresent()) {
|
||||||
|
return (AiFeedbackSummaryResponse) cachedResult.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AI 피드백 조회
|
||||||
|
var aiFeedback = analyticsPort.findAIFeedbackByStoreId(storeId);
|
||||||
|
|
||||||
|
if (aiFeedback.isEmpty()) {
|
||||||
|
// 3. 피드백이 없으면 기본 응답 생성
|
||||||
|
AiFeedbackSummaryResponse emptyResponse = AiFeedbackSummaryResponse.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.hasData(false)
|
||||||
|
.message("분석할 데이터가 부족합니다.")
|
||||||
|
.lastUpdated(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||||
|
return emptyResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 응답 생성
|
||||||
|
AiFeedbackSummaryResponse response = AiFeedbackSummaryResponse.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.hasData(true)
|
||||||
|
.message("AI 분석이 완료되었습니다.")
|
||||||
|
.overallScore(aiFeedback.get().getConfidenceScore())
|
||||||
|
.keyInsight(aiFeedback.get().getSummary())
|
||||||
|
.priorityRecommendation(getFirstRecommendation(aiFeedback.get()))
|
||||||
|
.lastUpdated(aiFeedback.get().getUpdatedAt())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 5. 캐시에 저장
|
||||||
|
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(2));
|
||||||
|
|
||||||
|
log.info("AI 피드백 요약 조회 완료: storeId={}", storeId);
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("AI 피드백 요약 조회 중 오류 발생: storeId={}", storeId, e);
|
||||||
|
throw new RuntimeException("AI 피드백 요약 조회에 실패했습니다.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReviewAnalysisResponse getReviewAnalysis(Long storeId) {
|
public ReviewAnalysisResponse getReviewAnalysis(Long storeId) {
|
||||||
// 이전 구현과 동일
|
log.info("리뷰 분석 조회 시작: storeId={}", storeId);
|
||||||
return null; // 구현 생략
|
|
||||||
|
try {
|
||||||
|
// 1. 캐시에서 확인
|
||||||
|
String cacheKey = "review_analysis:store:" + storeId;
|
||||||
|
var cachedResult = cachePort.getAnalyticsCache(cacheKey);
|
||||||
|
if (cachedResult.isPresent()) {
|
||||||
|
return (ReviewAnalysisResponse) cachedResult.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 최근 리뷰 데이터 조회 (30일)
|
||||||
|
List<String> recentReviews = externalReviewPort.getRecentReviews(storeId, 30);
|
||||||
|
|
||||||
|
if (recentReviews.isEmpty()) {
|
||||||
|
ReviewAnalysisResponse emptyResponse = ReviewAnalysisResponse.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.totalReviews(0)
|
||||||
|
.positiveReviewCount(0)
|
||||||
|
.negativeReviewCount(0)
|
||||||
|
.positiveRate(0.0)
|
||||||
|
.negativeRate(0.0)
|
||||||
|
.analysisDate(LocalDate.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
cachePort.putAnalyticsCache(cacheKey, emptyResponse, java.time.Duration.ofHours(1));
|
||||||
|
return emptyResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 응답 생성
|
||||||
|
int positiveCount = countPositiveReviews(recentReviews);
|
||||||
|
int negativeCount = countNegativeReviews(recentReviews);
|
||||||
|
int totalCount = recentReviews.size();
|
||||||
|
|
||||||
|
ReviewAnalysisResponse response = ReviewAnalysisResponse.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.totalReviews(totalCount)
|
||||||
|
.positiveReviewCount(positiveCount)
|
||||||
|
.negativeReviewCount(negativeCount)
|
||||||
|
.positiveRate((double) positiveCount / totalCount * 100)
|
||||||
|
.negativeRate((double) negativeCount / totalCount * 100)
|
||||||
|
.analysisDate(LocalDate.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 캐시에 저장
|
||||||
|
cachePort.putAnalyticsCache(cacheKey, response, java.time.Duration.ofHours(4));
|
||||||
|
|
||||||
|
log.info("리뷰 분석 조회 완료: storeId={}", storeId);
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("리뷰 분석 중 오류 발생: storeId={}", storeId, e);
|
||||||
|
throw new RuntimeException("리뷰 분석에 실패했습니다.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private 메서드들
|
// private 메서드들
|
||||||
@Transactional
|
@Transactional
|
||||||
private Analytics generateNewAnalytics(Long storeId) {
|
public Analytics generateNewAnalytics(Long storeId) {
|
||||||
// 이전 구현과 동일
|
log.info("새로운 분석 데이터 생성 시작: storeId={}", storeId);
|
||||||
return null; // 구현 생략
|
|
||||||
|
try {
|
||||||
|
// 1. 리뷰 데이터 수집
|
||||||
|
List<String> reviewData = externalReviewPort.getReviewData(storeId);
|
||||||
|
int totalReviews = reviewData.size();
|
||||||
|
|
||||||
|
if (totalReviews == 0) {
|
||||||
|
log.warn("리뷰 데이터가 없어 기본값으로 분석 데이터 생성: storeId={}", storeId);
|
||||||
|
return createDefaultAnalytics(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 기본 통계 계산
|
||||||
|
double averageRating = 4.0; // 기본값
|
||||||
|
double sentimentScore = 0.5; // 중립
|
||||||
|
double positiveRate = 60.0;
|
||||||
|
double negativeRate = 20.0;
|
||||||
|
|
||||||
|
// 3. Analytics 도메인 객체 생성
|
||||||
|
Analytics analytics = Analytics.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.totalReviews(totalReviews)
|
||||||
|
.averageRating(averageRating)
|
||||||
|
.sentimentScore(sentimentScore)
|
||||||
|
.positiveReviewRate(positiveRate)
|
||||||
|
.negativeReviewRate(negativeRate)
|
||||||
|
.lastAnalysisDate(LocalDateTime.now())
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.updatedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 데이터베이스에 저장
|
||||||
|
Analytics saved = analyticsPort.saveAnalytics(analytics);
|
||||||
|
|
||||||
|
log.info("새로운 분석 데이터 생성 완료: storeId={}", storeId);
|
||||||
|
return saved;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("분석 데이터 생성 중 오류 발생: storeId={}", storeId, e);
|
||||||
|
return createDefaultAnalytics(storeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
private AiFeedback generateAIFeedback(Long storeId) {
|
public AiFeedback generateAIFeedback(Long storeId) {
|
||||||
// 이전 구현과 동일
|
log.info("AI 피드백 생성 시작: storeId={}", storeId);
|
||||||
return null; // 구현 생략
|
|
||||||
|
try {
|
||||||
|
// 1. 최근 30일 리뷰 데이터 수집
|
||||||
|
List<String> reviewData = externalReviewPort.getRecentReviews(storeId, 30);
|
||||||
|
|
||||||
|
if (reviewData.isEmpty()) {
|
||||||
|
log.warn("AI 피드백 생성을 위한 리뷰 데이터가 없습니다: storeId={}", storeId);
|
||||||
|
return createDefaultAIFeedback(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AI 피드백 생성 (실제로는 AI 서비스 호출)
|
||||||
|
AiFeedback aiFeedback = AiFeedback.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.summary("고객들의 전반적인 만족도가 높습니다.")
|
||||||
|
.positivePoints(List.of("맛이 좋다", "서비스가 친절하다", "분위기가 좋다"))
|
||||||
|
.improvementPoints(List.of("대기시간 단축", "가격 경쟁력", "메뉴 다양성"))
|
||||||
|
.recommendations(List.of("특별 메뉴 개발", "예약 시스템 도입", "고객 서비스 교육"))
|
||||||
|
.sentimentAnalysis("POSITIVE")
|
||||||
|
.confidenceScore(0.85)
|
||||||
|
.generatedAt(LocalDateTime.now())
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.updatedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 3. 데이터베이스에 저장
|
||||||
|
AiFeedback saved = analyticsPort.saveAIFeedback(aiFeedback);
|
||||||
|
|
||||||
|
log.info("AI 피드백 생성 완료: storeId={}", storeId);
|
||||||
|
return saved;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("AI 피드백 생성 중 오류 발생: storeId={}", storeId, e);
|
||||||
|
return createDefaultAIFeedback(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Analytics createDefaultAnalytics(Long storeId) {
|
||||||
|
return Analytics.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.totalReviews(0)
|
||||||
|
.averageRating(0.0)
|
||||||
|
.sentimentScore(0.0)
|
||||||
|
.positiveReviewRate(0.0)
|
||||||
|
.negativeReviewRate(0.0)
|
||||||
|
.lastAnalysisDate(LocalDateTime.now())
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.updatedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AiFeedback createDefaultAIFeedback(Long storeId) {
|
||||||
|
return AiFeedback.builder()
|
||||||
|
.storeId(storeId)
|
||||||
|
.summary("분석할 리뷰 데이터가 부족합니다.")
|
||||||
|
.positivePoints(List.of("데이터 부족으로 분석 불가"))
|
||||||
|
.improvementPoints(List.of("리뷰 데이터 수집 필요"))
|
||||||
|
.recommendations(List.of("고객들의 리뷰 작성을 유도해보세요"))
|
||||||
|
.sentimentAnalysis("NEUTRAL")
|
||||||
|
.confidenceScore(0.0)
|
||||||
|
.generatedAt(LocalDateTime.now())
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.updatedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFirstRecommendation(AiFeedback feedback) {
|
||||||
|
if (feedback.getRecommendations() != null && !feedback.getRecommendations().isEmpty()) {
|
||||||
|
return feedback.getRecommendations().get(0);
|
||||||
|
}
|
||||||
|
return "추천사항이 없습니다.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countPositiveReviews(List<String> reviews) {
|
||||||
|
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||||
|
return (int) (reviews.size() * 0.6); // 60% 가정
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countNegativeReviews(List<String> reviews) {
|
||||||
|
// 실제로는 AI 서비스를 통한 감정 분석 필요
|
||||||
|
return (int) (reviews.size() * 0.2); // 20% 가정
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics 서비스 보안 설정 클래스
|
* Analytics 서비스 보안 설정 클래스
|
||||||
@ -14,12 +17,17 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
// Swagger 관련 경로 모두 허용
|
// Swagger 관련 경로 모두 허용
|
||||||
|
|||||||
@ -23,5 +23,23 @@ public class SwaggerConfig {
|
|||||||
.description("하이오더 분석 서비스 API 문서")
|
.description("하이오더 분석 서비스 API 문서")
|
||||||
.version("1.0.0"));
|
.version("1.0.0"));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* JWT Bearer 토큰을 위한 Security Scheme 생성
|
||||||
|
*/
|
||||||
|
private SecurityScheme createAPIKeyScheme() {
|
||||||
|
return new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.in(SecurityScheme.In.HEADER)
|
||||||
|
.name("Authorization")
|
||||||
|
.description("""
|
||||||
|
JWT 토큰을 입력하세요
|
||||||
|
|
||||||
|
사용법:
|
||||||
|
1. 로그인 API로 토큰 발급
|
||||||
|
2. Bearer 접두사 없이 토큰만 입력
|
||||||
|
3. 예: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOi...
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ spring:
|
|||||||
|
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:create}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
show-sql: ${JPA_SHOW_SQL:false}
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
@ -92,4 +92,4 @@ azure:
|
|||||||
ai-analysis-events: ${AZURE_EVENTHUB_AI_ANALYSIS_EVENTS:ai-analysis-events}
|
ai-analysis-events: ${AZURE_EVENTHUB_AI_ANALYSIS_EVENTS:ai-analysis-events}
|
||||||
storage:
|
storage:
|
||||||
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=your-storage-key;EndpointSuffix=core.windows.net}
|
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=your-storage-key;EndpointSuffix=core.windows.net}
|
||||||
container-name: ${AZURE_STORAGE_CONTAINER_NAME:eventhub-checkpoints}
|
container-name: ${AZURE_STORAGE_CONTAINER_NAME:eventhub-checkpoints}
|
||||||
|
|||||||
102
common/src/main/java/com/ktds/hi/common/config/CorsConfig.java
Normal file
102
common/src/main/java/com/ktds/hi/common/config/CorsConfig.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package com.ktds.hi.common.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 서비스 통합 CORS 설정 클래스
|
||||||
|
* 모든 마이크로서비스에서 공통으로 사용되는 CORS 정책을 정의
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value("${app.cors.allowed-origins:http://20.214.126.84,http://localhost:3000}")
|
||||||
|
private String allowedOrigins;
|
||||||
|
|
||||||
|
@Value("${app.cors.allowed-methods:GET,POST,PUT,DELETE,OPTIONS}")
|
||||||
|
private String allowedMethods;
|
||||||
|
|
||||||
|
@Value("${app.cors.allowed-headers:*}")
|
||||||
|
private String allowedHeaders;
|
||||||
|
|
||||||
|
@Value("${app.cors.exposed-headers:Authorization,X-Total-Count}")
|
||||||
|
private String exposedHeaders;
|
||||||
|
|
||||||
|
@Value("${app.cors.allow-credentials:true}")
|
||||||
|
private boolean allowCredentials;
|
||||||
|
|
||||||
|
@Value("${app.cors.max-age:3600}")
|
||||||
|
private long maxAge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebMvcConfigurer를 통한 CORS 설정
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOriginPatterns(allowedOrigins.split(","))
|
||||||
|
.allowedMethods(allowedMethods.split(","))
|
||||||
|
.allowedHeaders(allowedHeaders.split(","))
|
||||||
|
.exposedHeaders(exposedHeaders.split(","))
|
||||||
|
.allowCredentials(allowCredentials)
|
||||||
|
.maxAge(maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CorsConfigurationSource Bean 생성
|
||||||
|
* Spring Security와 함께 사용되는 CORS 설정
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
|
||||||
|
// Origin 설정
|
||||||
|
List<String> origins = Arrays.asList(allowedOrigins.split(","));
|
||||||
|
configuration.setAllowedOriginPatterns(origins);
|
||||||
|
|
||||||
|
// Method 설정
|
||||||
|
List<String> methods = Arrays.asList(allowedMethods.split(","));
|
||||||
|
configuration.setAllowedMethods(methods);
|
||||||
|
|
||||||
|
// Header 설정
|
||||||
|
if ("*".equals(allowedHeaders)) {
|
||||||
|
configuration.addAllowedHeader("*");
|
||||||
|
} else {
|
||||||
|
List<String> headers = Arrays.asList(allowedHeaders.split(","));
|
||||||
|
configuration.setAllowedHeaders(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exposed Headers 설정
|
||||||
|
List<String> exposed = Arrays.asList(exposedHeaders.split(","));
|
||||||
|
configuration.setExposedHeaders(exposed);
|
||||||
|
|
||||||
|
// Credentials 설정
|
||||||
|
configuration.setAllowCredentials(allowCredentials);
|
||||||
|
|
||||||
|
// Max Age 설정
|
||||||
|
configuration.setMaxAge(maxAge);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CorsFilter Bean 생성
|
||||||
|
* 글로벌 CORS 필터로 사용
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
return new CorsFilter(corsConfigurationSource());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ spring:
|
|||||||
# JPA 설정
|
# JPA 설정
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
naming:
|
naming:
|
||||||
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
||||||
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
|
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
|
||||||
@ -51,13 +51,12 @@ app:
|
|||||||
secret-key: ${JWT_SECRET_KEY:hiorder-secret-key-for-jwt-token-generation-2024-very-long-secret-key}
|
secret-key: ${JWT_SECRET_KEY:hiorder-secret-key-for-jwt-token-generation-2024-very-long-secret-key}
|
||||||
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600000} # 1시간
|
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600000} # 1시간
|
||||||
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:604800000} # 7일
|
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:604800000} # 7일
|
||||||
|
|
||||||
# CORS 설정
|
# CORS 설정
|
||||||
cors:
|
cors:
|
||||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
|
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://20.214.126.84,http://localhost:8080}
|
||||||
allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS}
|
allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS}
|
||||||
allowed-headers: ${CORS_ALLOWED_HEADERS:*}
|
allowed-headers: ${CORS_ALLOWED_HEADERS:*}
|
||||||
exposed-headers: ${CORS_EXPOSED_HEADERS:Authorization}
|
exposed-headers: ${CORS_EXPOSED_HEADERS:Authorization, X-Total-Count}
|
||||||
allow-credentials: ${CORS_ALLOW_CREDENTIALS:true}
|
allow-credentials: ${CORS_ALLOW_CREDENTIALS:true}
|
||||||
max-age: ${CORS_MAX_AGE:3600}
|
max-age: ${CORS_MAX_AGE:3600}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.ktds.hi.common.security.JwtTokenProvider;
|
import com.ktds.hi.common.security.JwtTokenProvider;
|
||||||
import com.ktds.hi.common.security.JwtAuthenticationFilter;
|
import com.ktds.hi.common.security.JwtAuthenticationFilter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
@ -16,6 +18,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 설정 클래스
|
* Spring Security 설정 클래스
|
||||||
@ -27,6 +30,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 보안 필터 체인 설정
|
* 보안 필터 체인 설정
|
||||||
@ -36,9 +40,10 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(csrf -> csrf.disable())
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(authz -> authz
|
.authorizeHttpRequests(authz -> authz
|
||||||
.requestMatchers("/api/auth/**", "/api/members/register").permitAll()
|
.requestMatchers("/api/auth/**", "/api/members/register", "/api/auth/login").permitAll()
|
||||||
.requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
|
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
|
||||||
.requestMatchers("/actuator/**").permitAll()
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.ktds.hi.member.config;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -22,4 +23,17 @@ public class SwaggerConfig {
|
|||||||
.description("회원 가입, 로그인, 취향 관리 등 회원 관련 기능을 제공하는 API")
|
.description("회원 가입, 로그인, 취향 관리 등 회원 관련 기능을 제공하는 API")
|
||||||
.version("1.0.0"));
|
.version("1.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Bearer 토큰을 위한 Security Scheme 생성
|
||||||
|
*/
|
||||||
|
private SecurityScheme createAPIKeyScheme() {
|
||||||
|
return new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.in(SecurityScheme.In.HEADER)
|
||||||
|
.name("Authorization")
|
||||||
|
.description("JWT 토큰을 입력하세요 (Bearer 접두사 제외)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ spring:
|
|||||||
|
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:create}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
show-sql: ${JPA_SHOW_SQL:false}
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.ktds.hi.recommend.infra.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
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.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics 서비스 보안 설정 클래스
|
||||||
|
* 테스트를 위해 모든 엔드포인트를 인증 없이 접근 가능하도록 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
|
||||||
|
http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// Swagger 관련 경로 모두 허용
|
||||||
|
.requestMatchers("/swagger-ui.html","/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||||
|
.requestMatchers("/api-docs/**", "/v3/api-docs/**").permitAll()
|
||||||
|
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
|
||||||
|
|
||||||
|
// Analytics API 모두 허용 (테스트용)
|
||||||
|
.requestMatchers("/api/analytics/**").permitAll()
|
||||||
|
.requestMatchers("/api/action-plans/**").permitAll()
|
||||||
|
|
||||||
|
// Actuator 엔드포인트 허용
|
||||||
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
|
||||||
|
// 기타 모든 요청 허용 (테스트용)
|
||||||
|
.anyRequest().permitAll()
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package com.ktds.hi.recommend.infra.config;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -23,4 +24,24 @@ public class SwaggerConfig {
|
|||||||
.description("사용자 취향 기반 매장 추천 및 취향 분석 관련 기능을 제공하는 API")
|
.description("사용자 취향 기반 매장 추천 및 취향 분석 관련 기능을 제공하는 API")
|
||||||
.version("1.0.0"));
|
.version("1.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Bearer 토큰을 위한 Security Scheme 생성
|
||||||
|
*/
|
||||||
|
private SecurityScheme createAPIKeyScheme() {
|
||||||
|
return new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.in(SecurityScheme.In.HEADER)
|
||||||
|
.name("Authorization")
|
||||||
|
.description("""
|
||||||
|
JWT 토큰을 입력하세요
|
||||||
|
|
||||||
|
사용법:
|
||||||
|
1. 로그인 API로 토큰 발급
|
||||||
|
2. Bearer 접두사 없이 토큰만 입력
|
||||||
|
3. 예: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOi...
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ spring:
|
|||||||
# JPA 설정
|
# JPA 설정
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:create}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
show-sql: ${JPA_SHOW_SQL:false}
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
@ -132,7 +132,6 @@ management:
|
|||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
path: /api-docs
|
path: /api-docs
|
||||||
enabled: true
|
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
path: /swagger-ui.html
|
path: /swagger-ui.html
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
|
|||||||
@ -65,9 +65,8 @@ public class ReviewInteractor implements CreateReviewUseCase, DeleteReviewUseCas
|
|||||||
public ReviewDeleteResponse deleteReview(Long reviewId, Long memberId) {
|
public ReviewDeleteResponse deleteReview(Long reviewId, Long memberId) {
|
||||||
Review review = reviewRepository.findReviewByIdAndMemberId(reviewId, memberId)
|
Review review = reviewRepository.findReviewByIdAndMemberId(reviewId, memberId)
|
||||||
.orElseThrow(() -> new BusinessException("리뷰를 찾을 수 없거나 권한이 없습니다"));
|
.orElseThrow(() -> new BusinessException("리뷰를 찾을 수 없거나 권한이 없습니다"));
|
||||||
|
|
||||||
Review deletedReview = review.updateStatus(ReviewStatus.DELETED);
|
reviewRepository.deleteReview(reviewId);
|
||||||
reviewRepository.saveReview(deletedReview);
|
|
||||||
|
|
||||||
log.info("리뷰 삭제 완료: reviewId={}, memberId={}", reviewId, memberId);
|
log.info("리뷰 삭제 완료: reviewId={}, memberId={}", reviewId, memberId);
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics 서비스 보안 설정 클래스
|
* Analytics 서비스 보안 설정 클래스
|
||||||
@ -14,12 +17,16 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
// Swagger 관련 경로 모두 허용
|
// Swagger 관련 경로 모두 허용
|
||||||
|
|||||||
@ -1,26 +1,34 @@
|
|||||||
package com.ktds.hi.review.infra.config;
|
package com.ktds.hi.review.infra.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Swagger 설정 클래스
|
|
||||||
* API 문서화를 위한 OpenAPI 설정
|
|
||||||
*/
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SwaggerConfig {
|
public class SwaggerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI openAPI() {
|
public OpenAPI openAPI() {
|
||||||
|
final String securitySchemeName = "Bearer Authentication";
|
||||||
|
|
||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
.addServersItem(new Server().url("/"))
|
.addServersItem(new Server().url("/"))
|
||||||
.info(new Info()
|
.info(new Info()
|
||||||
.title("하이오더 리뷰 관리 서비스 API")
|
.title("하이오더 리뷰 관리 서비스 API")
|
||||||
.description("리뷰 작성, 조회, 삭제, 반응, 댓글 등 리뷰 관련 기능을 제공하는 API")
|
.description("리뷰 작성, 조회, 삭제, 반응, 댓글 등 리뷰 관련 기능을 제공하는 API")
|
||||||
.version("1.0.0"));
|
.version("1.0.0"))
|
||||||
|
.addSecurityItem(new SecurityRequirement()
|
||||||
|
.addList(securitySchemeName))
|
||||||
|
.components(new Components()
|
||||||
|
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
|
||||||
|
.name(securitySchemeName)
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ spring:
|
|||||||
|
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:create}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
show-sql: ${JPA_SHOW_SQL:false}
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics 서비스 보안 설정 클래스
|
* Analytics 서비스 보안 설정 클래스
|
||||||
@ -14,12 +17,16 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
// Swagger 관련 경로 모두 허용
|
// Swagger 관련 경로 모두 허용
|
||||||
|
|||||||
@ -13,7 +13,7 @@ spring:
|
|||||||
|
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${JPA_DDL_AUTO:create}
|
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||||
show-sql: ${JPA_SHOW_SQL:false}
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user