[JWT 필터 및 보안 설정 개선] JwtTokenValidationFilter에서 불필요한 의존성을 제거하고, WebSecurity에서 공개 및 API 경로에 대한 SecurityFilterChain을 추가하여 인증 경로를 명확히 설정함. application.properties에서 허용 경로 설정을 제거하여 코드 정리.
This commit is contained in:
		@@ -1,12 +1,10 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.filter;
 | 
					package com.bio.bio_backend.global.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					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.domain.base.member.dto.MemberDto;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.HttpUtils;
 | 
					import com.bio.bio_backend.global.utils.HttpUtils;
 | 
				
			||||||
import org.springframework.core.env.Environment;
 | 
					
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
					import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
				
			||||||
import org.springframework.security.core.context.SecurityContextHolder;
 | 
					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.domain.base.member.service.MemberService;
 | 
				
			||||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
					import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
import com.bio.bio_backend.global.config.SecurityPathConfig;
 | 
					
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,16 +33,8 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
 | 
				
			|||||||
    private final JwtUtils jwtUtils;
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
    private final HttpUtils httpUtils;
 | 
					    private final HttpUtils httpUtils;
 | 
				
			||||||
    private final MemberService memberService;
 | 
					    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
 | 
					    @Override
 | 
				
			||||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
 | 
					    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,8 @@ import com.bio.bio_backend.global.filter.JwtTokenIssuanceFilter;
 | 
				
			|||||||
import com.bio.bio_backend.global.filter.JwtTokenValidationFilter;
 | 
					import com.bio.bio_backend.global.filter.JwtTokenValidationFilter;
 | 
				
			||||||
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.core.env.Environment;
 | 
					import org.springframework.core.annotation.Order;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.security.authentication.AuthenticationManager;
 | 
					import org.springframework.security.authentication.AuthenticationManager;
 | 
				
			||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 | 
					import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 | 
				
			||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 | 
					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.exception.CustomAuthenticationFailureHandler;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.HttpUtils;
 | 
					import com.bio.bio_backend.global.utils.HttpUtils;
 | 
				
			||||||
import com.bio.bio_backend.global.config.SecurityPathConfig;
 | 
					 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Configuration
 | 
					@Configuration
 | 
				
			||||||
@@ -35,12 +35,10 @@ public class WebSecurity {
 | 
				
			|||||||
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
 | 
					    private final BCryptPasswordEncoder bCryptPasswordEncoder;
 | 
				
			||||||
    private final JwtUtils jwtUtils;
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
    private final ObjectMapper objectMapper;
 | 
					    private final ObjectMapper objectMapper;
 | 
				
			||||||
    private final Environment env;
 | 
					 | 
				
			||||||
    private final SecurityPathConfig securityPathConfig;
 | 
					 | 
				
			||||||
    private final HttpUtils httpUtils;
 | 
					    private final HttpUtils httpUtils;
 | 
				
			||||||
    private final MemberMapper memberMapper;
 | 
					    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);
 | 
					        JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, jwtUtils, objectMapper, memberService, httpUtils, memberMapper);
 | 
				
			||||||
        filter.setFilterProcessesUrl("/login");
 | 
					        filter.setFilterProcessesUrl("/login");
 | 
				
			||||||
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
 | 
					        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
 | 
				
			||||||
@@ -48,14 +46,62 @@ public class WebSecurity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private JwtTokenValidationFilter getJwtTokenValidationFilter() {
 | 
					    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
 | 
					    @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 설정
 | 
					        // AuthenticationManager 설정
 | 
				
			||||||
        AuthenticationManagerBuilder authenticationManagerBuilder =
 | 
					        AuthenticationManagerBuilder authenticationManagerBuilder =
 | 
				
			||||||
                http.getSharedObject(AuthenticationManagerBuilder.class);
 | 
					                http.getSharedObject(AuthenticationManagerBuilder.class);
 | 
				
			||||||
@@ -63,25 +109,16 @@ public class WebSecurity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
 | 
					        AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 설정 파일에서 허용할 경로 가져오기
 | 
					        http
 | 
				
			||||||
        String[] permitAllPaths = securityPathConfig.getPermitAllPaths().toArray(new String[0]);
 | 
					            .csrf(AbstractHttpConfigurer::disable)
 | 
				
			||||||
 | 
					            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
 | 
				
			||||||
        http.csrf(AbstractHttpConfigurer::disable)          //csrf 비활성화
 | 
					 | 
				
			||||||
                .authorizeHttpRequests(request ->               //request 허용 설정
 | 
					 | 
				
			||||||
                        request
 | 
					 | 
				
			||||||
                            .requestMatchers(permitAllPaths).permitAll()  // 설정 파일에서 허용할 경로
 | 
					 | 
				
			||||||
                            .anyRequest().authenticated()  // 나머지 요청은 인증 필요
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            .authenticationManager(authenticationManager)
 | 
					            .authenticationManager(authenticationManager)
 | 
				
			||||||
            .sessionManagement(session -> 
 | 
					            .sessionManagement(session -> 
 | 
				
			||||||
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
 | 
					                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
                .logout(AbstractHttpConfigurer::disable);
 | 
					            .logout(AbstractHttpConfigurer::disable)
 | 
				
			||||||
        http
 | 
					            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)  // 토큰 발급
 | 
				
			||||||
            // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
 | 
					            .addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);  // 토큰 검증
 | 
				
			||||||
            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
 | 
					 | 
				
			||||||
            // 2단계: JWT 토큰 검증 필터 (자동 토큰 갱신 포함)
 | 
					 | 
				
			||||||
            .addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return http.build();
 | 
					        return http.build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,10 +114,6 @@ springdoc.swagger-ui.disable-swagger-default-url=true
 | 
				
			|||||||
springdoc.default-produces-media-type=application/json
 | 
					springdoc.default-produces-media-type=application/json
 | 
				
			||||||
springdoc.default-consumes-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
 | 
					spring.servlet.multipart.enabled=true
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user