[Spring] filter 기능 구현 후 CORS 에러가 발생하는 이슈

Jinny·2023년 7월 21일
2

Trouble Shooting

목록 보기
4/8

Spring filter 기능 구현 후 CORS 에러가 발생하는 이슈

  • 팀 프로젝트를 진행하며 Spring Filter를 사용하여 로그인 기능 구현
  • CORS는 Configuration으로 설정

보다 상세한 코드는 Gihub에서 확인 가능하다.

Configuration 코드

  • 모든 url, origin, method를 해용해준 상태
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
    
    @Override  
    public void addCorsMappings(CorsRegistry registry) {  
        registry.addMapping("/**")  
         .allowedOrigins("*")  
         .allowedMethods("*")  
         .maxAge(3000);  
    }  
}

Filter 코드

public class JwtAuthorizationFilter implements Filter {

    // 생략

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        
		// api의 화이트리스트 여부 체크
        if(whiteListCheck(httpServletRequest.getRequestURI())){
            chain.doFilter(request, response);
            return;
        }

		// request header에 토큰 포함 여부 체크
        if(!isContainToken(httpServletRequest)){
            sendErrorApiResponse(response, new MalformedJwtException(""));
            return;
        }
		
        // 필터 로직 진행
        try {
            String token = getToken(httpServletRequest);
            Claims claims = jwtProvider.getClaims(token);
            request.setAttribute("memberId", claims.get("memberId"));
            chain.doFilter(request, response);
        } catch (RuntimeException e) {
            sendErrorApiResponse(response, e);
        }
    }
}

단계 별로 에러가 발생해서 단계별 Trouble Shooting 과정을 정리했다.



1) 첫 화면인 login 페이지는 CORS 에러가 발생하지 않지만 로그인 완료 이후 CORS 에러 발생

상황

  • POST 요청인 loginCORS 에러가 발생하지 않는데,
    로그인 완료 후 GET요청으로 메인 페이지(api/columns)로 리다이렉트되면서 CORS 에러 발생

에러 메시지


Access to fetch at 'http://localhost:8080/api/columns' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

의문

  • CORS 에러가 날 거면 다 같이 나지 왜 따로 따로 나는가?
  • preflight 요청은 CORS 에러가 안 뜨고 본요청만 CORS 에러가 발생하는가?

시도한 해결 방법

  • 팀 프로젝트를 시작하기 전에 WAS 미션을 진행하며 Spring 구조에 대해 공부하면서 봤던 구조가 생각났다.


    출처: https://ee-22-joo.tistory.com/20

  • FilterDispatcher Servlet 실행되기 전에 먼저 실행되니까 WebMvcConfigurer의 설정이 적용이 안되는 것 같다는 생각이 들었다.
    - 근데 왜 login 페이지 진입은 되는 것인지? 이해할 수가 없네...(답답)
  • 무튼 그래서 "Login Filter 이전에 CORS Filter를 넣어주면 되지 않을까?"라는 생각이 들어 WebConfig를 삭제하고 CORS Filter를 추가했다.

FilterConfig 코드

@Configuration  
public class FilterConfig {  

	// 추가한 필터
    @Bean  
    public FilterRegistrationBean corsFilter() {  
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();  
        filterRegistrationBean.setFilter(new CorsFilter());  
        filterRegistrationBean.setOrder(1);  // 첫번째 순서로 넣어주기
        return filterRegistrationBean;  
    }  

	// 기존 필터
    @Bean  
    public FilterRegistrationBean jwtAuthorizationFilter(ObjectMapper mapper) {  
        FilterRegistrationBean<Filter> filterRegistrationBean = new  
                FilterRegistrationBean<>();  
        filterRegistrationBean.setFilter(new JwtAuthorizationFilter(mapper));  
        filterRegistrationBean.setOrder(2);  // 순서 1에서 2로 변경
        return filterRegistrationBean;  
    }  
  
}

CorsFilter 코드

public class CorsFilter implements Filter {  
  
    @Override  
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
            throws IOException, ServletException, IOException {  
        HttpServletResponse response = (HttpServletResponse) res;  
        response.setHeader("Access-Control-Allow-Origin", "*");  
        response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS, GET, DELETE, PUT");  
        response.setHeader("Access-Control-Max-Age", "3600");  
        response.setHeader("Access-Control-Allow-Headers", "Authorization, x-requested-with, origin, content-type, accept");  
        chain.doFilter(req, res);  
    }  
  
}


2) CORS Filter → Login Filter 설정 이후에도 동일한 에러 발생

상황

  • 1)번 상황이랑 동일

api 요청할 때 preflight 요청이 계속 발생하여 preflight에 대해 공부하고 다시 Trouble Shooting 시작

원인(추측)

  • preflight 요청은 headerauthorization 헤더가 없어서 인증을 하지 못해 401 에러가 발생하고, 이로 인해 본 요청에서 무슨 오류가 생긴게 아닐까 생각했다.

의문

  • preflight 요청이 401이 발생했다는 것은 CORS는 통과한 것으로 이해된다.
    • 로그를 찍었을 때 Login Filter까지 들어온 것을 확인했기 때문이다.
  • 그러면 본요청도 CORS 에러가 나지 않아야 하는 것이 정상이 아닌가?
    • 본요청은 request 메시지를 파싱하는 순서가 다른가?
    • 예를 들면 header를 파싱하는 과정에서 allow-origin header를 authorization header 보다 늦게 파싱해서? (설마.. 브라우저가 얼마나 똑똑한데...)

시도한 해결 방법

  • OPTIONSpreflight 요청이 올 경우 필터 통과하는 코드 추가
  
  @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        	HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        
        // OPTIONS로 api 요청 시 필터 통과
		if (httpServletRequest.getMethod().equals("OPTIONS")) {   
    		return;
    	}

        // 이하 코드 생략...
        
			// api의 화이트리스트 여부 체크

			// request header에 토큰 포함 여부 체크
        
 			// 필터 로직 진행
    	}
        

결과

  • 여기까지 하고나니 CORS 에러는 없어졌지만 매우 찝찝하다.
  • 왜냐면 이게 왜 "돼"? 하는 상황이기 때문이다.
  • 과정을 상세히 기록하고 Springhttp 요청 흐름에 대한 지식을 더 쌓고 의문을 해결해야겠다.

💡 참고:

  • 401 에러가 난 부분은 JWT 인증 관련 에러인데 해당 에러에 대한 Trouble Shooting 과정은 블로그 글 → [JWT] SignatureException 에러에서 확인 가능하다.

글을 작성하다가 문득 팀원이 공유해주신 블로그 글을 다시 보면서
FilterBean으로 설정하면 Spring에서 Filter를 관리해주니까
CorsFilter를 구현하지 않고 처음 설정했던 WebMvcConfigure를 설정해주면 먹히지 않을까라는 생각이 갑자기 들었다.

지금은 팀프로젝트가 끝나 서버도 내려가고 당장 쌓인일이 많아서
3일 뒤 새로 시작하는 팀프로젝트 때 꼭 확인해봐야겠다.


profile
공부는 마라톤이다. 한꺼번에 많은 것을 하다 지치지 말고 조금씩, 꾸준히, 자주하자.

0개의 댓글