Skip to content
JeeIn Lee edited this page Jul 24, 2023 · 2 revisions

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

  • Spring Filter를 사용하여 로그인 기능 구현
  • CORS는 Configuration으로 설정

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 에러에서 확인 가능하다.

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