Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

화이트리스트 요청 시 토큰 검증 생략 로직 구현 #65

Merged
merged 3 commits into from
Dec 2, 2024
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
@@ -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;
Expand All @@ -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<String> 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);
}

}
23 changes: 4 additions & 19 deletions src/main/java/com/munecting/api/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -102,4 +86,5 @@ private void handleAccessDenied(HttpServletResponse response) throws IOException
log.warn("권한이 없는 사용자의 접근입니다.");
responseUtil.sendException(response, new ForbiddenException());
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ spring :
allowed-headers: "*"
path-pattern: "/**"

whitelist:
patterns: ${WHITELIST_PATTERNS}

auth:
header: Authorization
prefix: Bearer
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

}