3. WhiteList 관련 삽질
코드까지 전부 기록해 놓은 내용은 노션에서 확인할 수 있다.
해당 코드를 사용한 프로젝트는 깃허브에서 확인할 수 있다.
WhiteList << 별거 아닌 줄 알았는데 마지막까지 내 발목잡은 놈
시큐리티 특성(?)상 에러 원인을 찾기가 참 힘든 것 같다.
(공부 안하고 바로 도입했으니 시큐리티 특성이 아니라 내 특성인가)
타 API를 다루듯이 에러의 위치를 "정확히" 찝어내는데 까지 굉장히 번거롭고
놀랍게도 시큐리티 처음부터, 마지막까지, 정말 꾸준하게 애먹인놈은 저놈이다 저놈.
이 글에는 whitelist가 어떻게 다채롭게 나를 엿먹였는지 나열하며 정리해보겠다.
1. 카카오 로그인 성공...? 정말로?
맨 처음 카카오 로그인 api를 연결했었을 때 "localhost:8080/api/login" 경로로 진입했었고 카카오 로그인 페이지가 나와서 성공한 줄 알았다.
"localhost:8080/" 루트 경로로 진입했었을 때도 카카오 로그인 페이지가 나오기 전까진.
debug level에서 security 부분을 설정하고 로그를 봤을 때 [ "/" -> "/error" -> 카카오로그인 ] 순서로 카카오 로그인이 동작하고 있었다.
원인은 이러했다
"api/**" 는 물론이거니와 "/" 루트경로 또한 화이트리스트에 들어있었으나, 이 경로에 맞는 html 정적파일이 존재하지 않았다. 일반적으로 찾아보고 따라한 블로그는 대체로 백엔드/프론트엔드 나눠서 하는 프로젝트 형의 게시글이 아닌 "카카오 로그인 사용해보기" 등의 게시글이었고, 스프링 MVC를 따르는 그 게시글들은 html파일이 당연히 존재했으나 나는 존재하지 않았다.
따라서 "/" 루트경로에 대한 접근을 테스트 했을때, index.html이 없기때문에 404가 리턴됐을 것이고, 404 에러페이지는 화이트리스트에 존재하지 않기 때문에 로그인이 필요하다는 시큐리티 에러페이지로 리다이렉트 됐을 것이고, "/error"경로 또한 당연히 화이트리스트에 존재하지 않기 때문에 카카오 로그인페이지로 리다이렉트됐고, 결과적으로 나에게는 카카오 로그인 페이지가 보여졌다. 대충 1~2초 뒤에
결과적으로 원인을 분석하고 나니 해결할 필요는 없었다. 나는 api만 제공하면 되고, html 파일이 없어서 생긴 문제는 상관없기때문
다 된줄알았다.
2. Whitelist는 "두 번" 써야하더라.
로그인 할 때는 잘 만 나오던 "내 유저정보"가 타 api에서 "현재 로그인된 유저 (이 글에서 getCurrentUser 메소드)" 정보만 가져왔다 하면 전부 Anonymous가 나왔다. 로그를 수십개를 찍어보고 나서 찾은 원인은 별게 아니었다.
// SecurityConfig.java의 SecurityFilterChain 일부
// HTTP 요청에 대한 보안 규칙을 설정
.authorizeHttpRequests(auth -> auth
.requestMatchers(JwtConstants.WHITELIST).permitAll() // 지정된 경로들은 인증 없이 접근 허용
.requestMatchers("/admin/**").hasRole("ADMIN") // "/admin/**" 경로는 'ADMIN' 역할을 가진 사용자만 접근 가능
.anyRequest().authenticated()) // 그 외 모든 요청은 인증을 요구
public class JwtConstants {
public static final String key = "DG3K2NG9lK3T2FLfnO283HO1NFLAy9FGJ23UM9Rv923YRV923HT";
public static final int ACCESS_EXP_TIME = 10; // 10분
public static final int REFRESH_EXP_TIME = 60 * 24; // 24시간
public static final String JWT_HEADER = "Authorization";
public static final String JWT_TYPE = "Bearer ";
public static final String[] WHITELIST = {
"/api/comments",
"/api/comments/**",
"/api/images",
"/api/images/**",
"/api/posts",
"/api/posts/**",
"/api/reviews",
"/api/reviews/**",
"/api/auth",
"/api/auth/**",
"/api/places",
"/api/places/**",
"/signUp",
"/login",
"/refresh",
"/",
"/index.html",
"/swagger-ui.html",
"/api-docs/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/webjars/**"
};
}
내가 화이트리스트를 사용했던 방식은 위와 같다. 나 뿐만 아니라 다들 비슷할 것이다.
하지만 이곳에만 넣어서는 "절대" 안된다.
// JwtVerifyFilter 클래스 에서 shouldNotFilter 메소드 내용만 추출
@Component
public class JwtVerifyFilter extends OncePerRequestFilter {
// 필터를 거치지 않을 URL 및 메서드를 설정하고, true 를 return 하면 현재 필터를 건너뛰고 다음 필터로 이동
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String requestURI = request.getRequestURI();
String httpMethod = request.getMethod();
// 특정 경로에 대해 GET 요청과 POST, PUT, DELETE 요청을 모두 필터링하도록 설정
if (PatternMatchUtils.simpleMatch("/api/users/**", requestURI)) {
return false;
}
// 인증 code 로직은 필터링 하지 않음
if (PatternMatchUtils.simpleMatch("/api/auth/**", requestURI)) {
return true;
}
// POST, PUT, DELETE 요청이 아닌 경우 = GET 요청인 경우
if (!httpMethod.equalsIgnoreCase("POST") && !httpMethod.equalsIgnoreCase("PUT") && !httpMethod.equalsIgnoreCase("DELETE")) {
for (String pattern : JwtConstants.WHITELIST) {
if (PatternMatchUtils.simpleMatch(pattern, requestURI)) {
return true; // 화이트리스트에 포함된 경우 필터를 거치지 않음 : Get & 화이트리스트
}
}
return false; // 화이트리스트에 포함되지 않은 경우 필터를 거침 : Get이지만 화이트리스트 아님
}
// POST, PUT, DELETE 요청인 경우
for (String pattern : JwtConstants.WHITELIST) {
if (PatternMatchUtils.simpleMatch(pattern, requestURI)) {
return false; // 화이트리스트에 포함되더라도 필터를 거침
}
}
return false; // POST, PUT, DELETE 요청이면서 화이트리스트에 포함되지 않은 경우
}
}
여기도 넣어줘야 한다.
jwtVerifyFilter는 에서 말하는 "필터링"은 이전 게시글에서 작성했듯이 JWT에서 필요한 값을 추출해 SecurityContextHolder에 보관하는 정말 중요한 역할을 담당한다.
뿐만 아니라 단 하나의 화이트리스트로 모든 인증 여부를 다 다룰 수 없다.
이곳에서 HttpMethod까지 구분해가며 디테일하게 케이스를 나눠줘야 했다.
3. "api/**" 는 "api" 라는 경로를 포함하지 않는다.
하위경로만 나타낸다고 하더라. 몰랐다. 미리 알았으면 참 좋았을 것을
써놓고 보니까 별거 아니기도 하고 한심하기도 한데 당시에는 하루안에 해결한 문제가 하나도 없다.
시큐리티도 "공부하고" 써먹자 "구글링"만 해서 써먹지 말고...
'Back-End > SpringBoot' 카테고리의 다른 글
[SpringSecurity 뜯어보기] oAuth2 Resource 라이브러리와 Custom JWT 간의 호환 (1) | 2025.01.15 |
---|---|
[SpringSecurity 뜯어보기] oAuth2 라이브러리를 통한 인증객체 주입 (1) | 2024.12.28 |
2. JWT + oAuth2 카카오 로그인 삽질일기 (0) | 2024.06.28 |
1. JWT + oAuth2 카카오 로그인 삽질일기 (0) | 2024.06.28 |
0. JWT + oAuth2 카카오 로그인 삽질일기 (3) | 2024.06.07 |