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
Expand Up @@ -50,12 +50,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
*
* 인증 μ‹€νŒ¨ μ‹œμ—λ„ μš”μ²­μ„ μ°¨λ‹¨ν•˜μ§€ μ•Šκ³  λ‹€μŒ ν•„ν„°λ‘œ μ „λ‹¬ν•˜μ—¬,
* 인증이 ν•„μš”ν•˜μ§€ μ•Šμ€ μ—”λ“œν¬μΈνŠΈλŠ” μ •μƒμ μœΌλ‘œ 처리될 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.
*
* @param request HTTP μš”μ²­ 객체
* @param response HTTP 응닡 객체
* @param filterChain λ‹€μŒ ν•„ν„°λ₯Ό ν˜ΈμΆœν•˜κΈ° μœ„ν•œ ν•„ν„° 체인
* @throws ServletException ν•„ν„° 처리 쀑 μ„œλΈ”λ¦Ώ μ˜ˆμ™Έκ°€ λ°œμƒν•œ 경우
* @throws IOException μž…μΆœλ ₯ μ˜ˆμ™Έκ°€ λ°œμƒν•œ 경우
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
Expand Down Expand Up @@ -88,12 +82,14 @@ protected void doFilterInternal(HttpServletRequest request,
// 6. SecurityContext에 인증 정보 μ €μž₯
SecurityContextHolder.getContext().setAuthentication(authentication);

log.debug("JWT 인증 성곡: kakaoId = {}", kakaoId);
log.debug("JWT 인증 성곡: kakaoId = {}, URI = {}", kakaoId, request.getRequestURI());
}

} catch (Exception e) {
log.error("JWT 인증 쀑 였λ₯˜ λ°œμƒ: {}", e.getMessage());
log.error("JWT 인증 쀑 였λ₯˜ λ°œμƒ: URI = {}, error = {}", request.getRequestURI(), e.getMessage());
SecurityContextHolder.clearContext();

// 인증 μ‹€νŒ¨ν•΄λ„ μš”μ²­μ€ 계속 μ§„ν–‰ (SecurityConfigμ—μ„œ κΆŒν•œ 체크)
}

// 7. λ‹€μŒ ν•„ν„°λ‘œ μš”μ²­ 전달
Expand Down Expand Up @@ -127,10 +123,12 @@ private String extractTokenFromRequest(HttpServletRequest request) {
* 이λ₯Ό 톡해 λΆˆν•„μš”ν•œ 인증 처리λ₯Ό λ°©μ§€ν•˜κ³  μ„±λŠ₯을 κ°œμ„ ν•©λ‹ˆλ‹€.
*
* ν•„ν„° μ œμ™Έ λŒ€μƒ:
* - 인증 κ΄€λ ¨ API (/api/auth/*)
* - OAuth2 둜그인 경둜 (/login/oauth2/*, /oauth2/*)
* - 메인 νŽ˜μ΄μ§€ (/)
* - 정적 λ¦¬μ†ŒμŠ€ (/css/*, /js/*, /images/*)
* - Swagger λ¬Έμ„œ (/swagger-ui/*, /v3/api-docs/*)
* - ν—¬μŠ€μ²΄ν¬ (/actuator/health)
* - 곡개 API (둜그인 μƒνƒœ 체크, HotTopic 쑰회 λ“±)
*
* @param request ν•„ν„° 적용 μ—¬λΆ€λ₯Ό 확인할 HTTP μš”μ²­ 객체
* @return ν•„ν„° μ œμ™Έ μ—¬λΆ€ (true: μ œμ™Έ, false: 적용)
Expand All @@ -139,12 +137,47 @@ private String extractTokenFromRequest(HttpServletRequest request) {
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();

return path.startsWith("/api/auth/")
|| path.startsWith("/login/oauth2/")
|| path.startsWith("/oauth2/")
|| path.equals("/")
|| path.startsWith("/css/")
|| path.startsWith("/js/")
|| path.startsWith("/images/");
// OAuth2 κ΄€λ ¨ 경둜 μ œμ™Έ
if (path.startsWith("/login/oauth2/") || path.startsWith("/oauth2/")) {
return true;
}

// 정적 λ¦¬μ†ŒμŠ€ μ œμ™Έ
if (path.equals("/") ||
path.startsWith("/css/") ||
path.startsWith("/js/") ||
path.startsWith("/images/") ||
path.equals("/favicon.ico")) {
return true;
}

// Swagger κ΄€λ ¨ μ œμ™Έ
if (path.startsWith("/swagger-ui/") ||
path.startsWith("/v3/api-docs/") ||
path.startsWith("/api-docs/") ||
path.equals("/swagger.html")) {
return true;
}

// ν—¬μŠ€μ²΄ν¬ μ œμ™Έ
if (path.equals("/actuator/health")) {
return true;
}

// 곡개 API μ œμ™Έ
if (path.equals("/api/auth/status") ||
path.startsWith("/api/hottopic/")) {
return true;
}

// 기타 곡개 ν…ŒμŠ€νŠΈ μ—”λ“œν¬μΈνŠΈ
if (path.equals("/run-batch") ||
path.startsWith("/elasticsearch/") ||
path.startsWith("/monitoring/test/") ||
path.startsWith("/kakao/")) {
return true;
}

return false;
}
}
76 changes: 69 additions & 7 deletions SpringBoot/src/main/java/Baemin/News_Deliver/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,69 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// CSRF λΉ„ν™œμ„±ν™”
// CSRF λΉ„ν™œμ„±ν™” (JWT μ‚¬μš©)
.csrf(AbstractHttpConfigurer::disable)

// CORS μ„€μ •
.cors(cors -> cors.configurationSource(corsConfigurationSource()))

// μ„Έμ…˜ λ¬΄μƒνƒœ μ„€μ •
// μ„Έμ…˜ λ¬΄μƒνƒœ μ„€μ • (JWT μ‚¬μš©)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

// μš”μ²­ κΆŒν•œ μ„€μ •
.authorizeHttpRequests(auth -> auth
.requestMatchers("/**").permitAll() // μž„μ‹œλ‘œ λͺ¨λ“  μš”μ²­ ν—ˆμš©
// 곡개 μ ‘κ·Ό ν—ˆμš© (인증 λΆˆν•„μš”)
.requestMatchers("/").permitAll()
.requestMatchers("/api/auth/status").permitAll()
.requestMatchers("/login/oauth2/**").permitAll()
.requestMatchers("/oauth2/**").permitAll()

// Swagger 및 API λ¬Έμ„œ
.requestMatchers("/swagger-ui/**", "/swagger.html", "/v3/api-docs/**", "/api-docs/**").permitAll()

// 정적 λ¦¬μ†ŒμŠ€
.requestMatchers("/css/**", "/js/**", "/images/**", "/favicon.ico").permitAll()

// ν—¬μŠ€μ²΄ν¬ 및 λͺ¨λ‹ˆν„°λ§
.requestMatchers("/actuator/health").permitAll()

// ν…ŒμŠ€νŠΈμš© μ—”λ“œν¬μΈνŠΈ
.requestMatchers("/run-batch").permitAll()
.requestMatchers("/elasticsearch/**").permitAll()
.requestMatchers("/monitoring/test/**").permitAll()

// HotTopic 곡개 API (둜그인 없이도 쑰회 κ°€λŠ₯)
.requestMatchers("/api/hottopic/**").permitAll()

// 카카였 λ©”μ‹œμ§€ λ°œμ†‘ (μŠ€μΌ€μ€„λŸ¬μš© - λ‚΄λΆ€ 호좜만 ν—ˆμš©ν•˜λ„λ‘ IP μ œν•œν•  μ˜ˆμ •)
.requestMatchers("/kakao/send-message").permitAll()
.requestMatchers("/kakao/search-news").permitAll()
.requestMatchers("/kakao/getcron").permitAll()

// JWT 인증 ν•„μš” - Auth κ΄€λ ¨ API
.requestMatchers("/api/auth/refresh").permitAll() // λ¦¬ν”„λ ˆμ‹œλŠ” ν† ν°μœΌλ‘œ 인증
.requestMatchers("/api/auth/logout", "/api/auth/me").authenticated()

// JWT 인증 ν•„μš” - μ‚¬μš©μž μ„€μ • 관리
.requestMatchers("/api/setting/**").authenticated()

// JWT 인증 ν•„μš” - μ„œλΈŒ μ„œλΉ„μŠ€λ“€
.requestMatchers("/sub/**").authenticated()

// 기타 λͺ¨λ“  API μš”μ²­μ€ 인증 ν•„μš”
.anyRequest().authenticated()
)

// OAuth2 둜그인 μ„€μ •
.oauth2Login(oauth2 -> oauth2
.loginPage("/oauth2/authorization/kakao")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler((request, response, exception) -> {
response.setContentType("application/json");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("{\"error\":\"OAuth2 Login Failed\",\"message\":\"" + exception.getMessage() + "\"}");
})
Expand All @@ -63,14 +103,38 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
// JWT ν•„ν„° μΆ”κ°€
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

// 인증 μ‹€νŒ¨ μ‹œ 처리
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("{\"error\":\"Authentication Required\",\"message\":\"둜그인이 ν•„μš”ν•©λ‹ˆλ‹€.\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
response.getWriter().write("{\"error\":\"Access Denied\",\"message\":\"μ ‘κ·Ό κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.\"}");
})
)

.build();
}

// CORS μ„€μ •
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));

// ν”„λ‘œλ•μ…˜ ν™˜κ²½μ—μ„œλŠ” νŠΉμ • λ„λ©”μΈλ§Œ ν—ˆμš©
configuration.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:3000", // 개발용 React
"http://localhost:3001", // 개발용 React (포트 λ³€κ²½μ‹œ)
"http://localhost:5173", // 개발용 React
"https://merry-crepe-479d93.netlify.app", // ν”„λ‘œν† νƒ€μž… 배포 URL
"http://43.201.27.98" // AWS EC2 IP

));

configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
Expand All @@ -80,6 +144,4 @@ public CorsConfigurationSource corsConfigurationSource() {
source.registerCorsConfiguration("/**", configuration);
return source;
}


}