diff --git a/src/main/java/com/munecting/api/global/auth/filter/JwtAuthenticationFilter.java b/src/main/java/com/munecting/api/global/auth/filter/JwtAuthenticationFilter.java index 9d64b6f..4b89695 100644 --- a/src/main/java/com/munecting/api/global/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/munecting/api/global/auth/filter/JwtAuthenticationFilter.java @@ -1,8 +1,7 @@ package com.munecting.api.global.auth.filter; -import com.munecting.api.domain.user.dao.UserRepository; import com.munecting.api.global.auth.jwt.JwtProvider; -import com.munecting.api.global.error.exception.UnauthorizedException; +import com.munecting.api.global.util.AllowedPathPatternProvider; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -12,51 +11,56 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Optional; -import static com.munecting.api.global.common.dto.response.Status.INVALID_TOKEN; - @Slf4j @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; private final String authHeader; - private final String prefix; - private final UserRepository userRepository; + private final AllowedPathPatternProvider allowedPathPatternProvider; public JwtAuthenticationFilter( JwtProvider jwtProvider, @Value("${spring.security.auth.header}") String authHeader, - @Value("${spring.security.auth.prefix}") String prefix, - UserRepository userRepository + AllowedPathPatternProvider allowedPathPatternProvider ) { this.jwtProvider = jwtProvider; this.authHeader = authHeader; - this.prefix = prefix; - this.userRepository = userRepository; + this.allowedPathPatternProvider = allowedPathPatternProvider; } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws - ServletException, - IOException { + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + if (isPathWhitelisted(request)) { + filterChain.doFilter(request, response); + return; + } + processAuthentication(request); + filterChain.doFilter(request, response); + } + private boolean isPathWhitelisted(HttpServletRequest request) { + return allowedPathPatternProvider.isPathWhitelisted(request.getRequestURI()); + } + + private void processAuthentication(HttpServletRequest request) { Optional bearerToken = Optional.ofNullable(request.getHeader(authHeader)); bearerToken.ifPresent(it -> { String accessToken = jwtProvider.extractAccessToken(it); jwtProvider.validateAccessToken(accessToken); setAuthentication(accessToken); }); - filterChain.doFilter(request, response); } private void setAuthentication(String accessToken) { UsernamePasswordAuthenticationToken authentication = jwtProvider.getAuthentication(accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); } + } diff --git a/src/main/java/com/munecting/api/global/config/SecurityConfig.java b/src/main/java/com/munecting/api/global/config/SecurityConfig.java index 26d48c7..c0aa22a 100644 --- a/src/main/java/com/munecting/api/global/config/SecurityConfig.java +++ b/src/main/java/com/munecting/api/global/config/SecurityConfig.java @@ -5,6 +5,7 @@ import com.munecting.api.global.auth.jwt.JwtProvider; import com.munecting.api.global.error.exception.ForbiddenException; import com.munecting.api.global.error.exception.UnauthorizedException; +import com.munecting.api.global.util.AllowedPathPatternProvider; import com.munecting.api.global.util.ResponseUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -32,26 +33,9 @@ public class SecurityConfig { private final ResponseUtil responseUtil; private final ExceptionHandlerFilter exceptionHandlerFilter; private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final AllowedPathPatternProvider allowedPathPatternProvider; - private static final String[] ALLOWED_URL = { - "/api/auth/**", - "/error/**", - "/v2/api-docs", - "/swagger-resources", - "/swagger-resources/**", - "/configuration/ui", - "/configuration/security", - "/swagger-ui.html", - "/webjars/**", - "/v3/api-docs/**", - "/swagger-ui/**", - "/css/**","/images/**","/js/**","/favicon.ico", - "/api/musics/**", - "/api/address/**", - "/actuator/health" - }; - @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http @@ -71,7 +55,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // URL 권한 설정 .authorizeHttpRequests(auth -> auth - .requestMatchers(ALLOWED_URL).permitAll() + .requestMatchers(allowedPathPatternProvider.getWhitelistPatterns()).permitAll() .anyRequest().authenticated()) // filter @@ -102,4 +86,5 @@ private void handleAccessDenied(HttpServletResponse response) throws IOException log.warn("권한이 없는 사용자의 접근입니다."); responseUtil.sendException(response, new ForbiddenException()); } + } diff --git a/src/main/java/com/munecting/api/global/util/AllowedPathPatternProvider.java b/src/main/java/com/munecting/api/global/util/AllowedPathPatternProvider.java new file mode 100644 index 0000000..7ad504e --- /dev/null +++ b/src/main/java/com/munecting/api/global/util/AllowedPathPatternProvider.java @@ -0,0 +1,46 @@ +package com.munecting.api.global.util; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.server.PathContainer; +import org.springframework.stereotype.Component; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; + +import java.util.Arrays; + +@Component +public class AllowedPathPatternProvider { + + private final PathPatternParser patternParser; + private final String[] whitelistPatterns; + + public AllowedPathPatternProvider( + PathPatternParser patternParser, + @Value("${spring.security.whitelist.patterns}") + String[] whitelistPatterns + ) { + this.patternParser = patternParser; + this.whitelistPatterns = whitelistPatterns; + } + + public boolean isPathWhitelisted(final String path) { + PathContainer requestPath = parsePathContainer(path); + + return Arrays.stream(whitelistPatterns) + .map(this::parsePathPattern) + .anyMatch(whitePathPattern -> whitePathPattern.matches(requestPath)); + } + + private PathPattern parsePathPattern(String whitePattern) { + return patternParser.parse(whitePattern); + } + + private PathContainer parsePathContainer(String path) { + return PathContainer.parsePath(path); + } + + public String[] getWhitelistPatterns() { + return whitelistPatterns; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ec0e92f..00b22df 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -26,6 +26,9 @@ spring : allowed-headers: "*" path-pattern: "/**" + whitelist: + patterns: ${WHITELIST_PATTERNS} + auth: header: Authorization prefix: Bearer diff --git a/src/test/java/com/munecting/api/global/util/AllowedPathPatternProviderTest.java b/src/test/java/com/munecting/api/global/util/AllowedPathPatternProviderTest.java new file mode 100644 index 0000000..6fd2689 --- /dev/null +++ b/src/test/java/com/munecting/api/global/util/AllowedPathPatternProviderTest.java @@ -0,0 +1,34 @@ +package com.munecting.api.global.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +class AllowedPathPatternProviderTest { + + @Autowired + private AllowedPathPatternProvider patternProvider; + + @DisplayName("주어진 경로가 화이트리스트에 등록되어 있는지 확인한다.") + @Test + public void isPathWhitelisted(){ + //given + String requestPath1 = "/api/auth/allowed"; + String requestPath2 = "/actuator/health/not-allowed"; + + //when + boolean result1 = patternProvider.isPathWhitelisted(requestPath1); + boolean result2 = patternProvider.isPathWhitelisted(requestPath2); + + //then + assertThat(result1).isTrue(); + assertThat(result2).isFalse(); + } + +} \ No newline at end of file