Skip to content

Commit

Permalink
Merge pull request #65 from Mu-necting/fix/#64
Browse files Browse the repository at this point in the history
화이트리스트 요청 시 토큰 검증 생략 로직 구현
  • Loading branch information
mingmingmon authored Dec 2, 2024
2 parents 1db7877 + a1a0807 commit 0fa29b3
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 34 deletions.
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();
}

}

0 comments on commit 0fa29b3

Please sign in to comment.