[JWT 필터 및 보안 설정 개선] JwtTokenValidationFilter에서 불필요한 의존성을 제거하고, WebSecurity에서 공개 및 API 경로에 대한 SecurityFilterChain을 추가하여 인증 경로를 명확히 설정함. application.properties에서 허용 경로 설정을 제거하여 코드 정리.

This commit is contained in:
2025-09-05 16:38:25 +09:00
parent f489fa3e51
commit cd689211ec
3 changed files with 66 additions and 43 deletions

View File

@@ -1,12 +1,10 @@
package com.bio.bio_backend.global.filter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Objects;
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import com.bio.bio_backend.global.utils.HttpUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -24,7 +22,7 @@ import com.bio.bio_backend.global.dto.ApiResponseDto;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.utils.JwtUtils;
import com.bio.bio_backend.global.config.SecurityPathConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -35,16 +33,8 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final HttpUtils httpUtils;
private final MemberService memberService;
private final Environment env;
private final SecurityPathConfig securityPathConfig;
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getRequestURI();
String contextPath = env.getProperty("server.servlet.context-path", "");
return securityPathConfig.isPermittedPath(path, contextPath);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,

View File

@@ -4,7 +4,8 @@ import com.bio.bio_backend.global.filter.JwtTokenIssuanceFilter;
import com.bio.bio_backend.global.filter.JwtTokenValidationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -23,7 +24,6 @@ import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
import com.bio.bio_backend.global.utils.JwtUtils;
import com.bio.bio_backend.global.utils.HttpUtils;
import com.bio.bio_backend.global.config.SecurityPathConfig;
import lombok.RequiredArgsConstructor;
@Configuration
@@ -35,12 +35,10 @@ public class WebSecurity {
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final JwtUtils jwtUtils;
private final ObjectMapper objectMapper;
private final Environment env;
private final SecurityPathConfig securityPathConfig;
private final HttpUtils httpUtils;
private final MemberMapper memberMapper;
private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception {
private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) {
JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, jwtUtils, objectMapper, memberService, httpUtils, memberMapper);
filter.setFilterProcessesUrl("/login");
filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
@@ -48,14 +46,62 @@ public class WebSecurity {
}
private JwtTokenValidationFilter getJwtTokenValidationFilter() {
return new JwtTokenValidationFilter(jwtUtils, httpUtils, memberService, env, securityPathConfig);
return new JwtTokenValidationFilter(jwtUtils, httpUtils, memberService);
}
/**
* 공개 경로용 SecurityFilterChain (우선순위 1)
*
* 처리 경로:
* - 인증 관련: /login, /logout, /members/register
* - API 문서: /swagger-ui/**, /api-docs/**
* - WebSocket: /ws/**
* - 모니터링: /actuator/**
*
* 모든 경로는 인증 없이 접근 가능
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@Order(1)
SecurityFilterChain publicChain(HttpSecurity http) throws Exception {
http
.securityMatcher(
// 인증 관련 공개 경로
"/logout", "/members/register",
// Swagger UI 관련 경로
"/swagger-ui/**", "/swagger-ui.html", "/swagger-ui/index.html",
// API 문서 관련 경로
"/api-docs", "/api-docs/**", "/v3/api-docs", "/v3/api-docs/**",
// WebSocket 관련 경로
"/ws/**",
// Actuator 모니터링 경로
"/actuator/**", "/actuator/health/**", "/actuator/info"
)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
/**
* API 경로용 SecurityFilterChain (우선순위 2)
*
* 처리 경로: publicChain에서 처리되지 않는 모든 경로
* - 비즈니스 API 엔드포인트
* - 사용자 데이터 관련 API
* - 관리자 기능 API
*
* 모든 요청에 대해 JWT 토큰 인증 필수
*/
@Bean
@Order(2)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
// AuthenticationManager 설정
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
@@ -63,25 +109,16 @@ public class WebSecurity {
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
// 설정 파일에서 허용할 경로 가져오기
String[] permitAllPaths = securityPathConfig.getPermitAllPaths().toArray(new String[0]);
http.csrf(AbstractHttpConfigurer::disable) //csrf 비활성화
.authorizeHttpRequests(request -> //request 허용 설정
request
.requestMatchers(permitAllPaths).permitAll() // 설정 파일에서 허용할 경로
.anyRequest().authenticated() // 나머지 요청은 인증 필요
)
.authenticationManager(authenticationManager)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 안함
)
.logout(AbstractHttpConfigurer::disable);
http
// 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
.addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
// 2단계: JWT 토큰 검증 필터 (자동 토큰 갱신 포함)
.addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.authenticationManager(authenticationManager)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.logout(AbstractHttpConfigurer::disable)
.addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class) // 토큰 발급
.addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class); // 토큰 검증
return http.build();
}

View File

@@ -114,10 +114,6 @@ springdoc.swagger-ui.disable-swagger-default-url=true
springdoc.default-produces-media-type=application/json
springdoc.default-consumes-media-type=application/json
# ========================================
# 보안 설정 - 허용할 경로
security.permit-all-paths=/login,/logout,/members/register,/swagger-ui/**,/swagger-ui.html,/swagger-ui/index.html,/api-docs,/api-docs/**,/v3/api-docs,/v3/api-docs/**,/ws/**,/actuator/**,/actuator/health/**,/actuator/info
# 파일 업로드 설정
# ========================================
spring.servlet.multipart.enabled=true