다른 문제는 큰 문제 없이 해결했으나, 프론트엔드와의 연동 중 CORS
로 인해 상당히 많은 시간을 소모해야 했습니다.
Spring Session
을 통해 Redis
로 Session
을 관리하고, Session ID
는 기본 설정값인 SESSION
이라는 이름의 쿠키로 전달하는 방식으로 진행했습니다.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "36000");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
현재 Spring Security OAuth2 Client
로 소셜 로그인을 처리하고 있었기 때문에 위와 같이 별도의 Filter
를 만들어 @Order(Ordered.HIGHEST_PRECEDENCE)
를 통해 최우선적으로 요청이 해당 필터를 거쳐가도록 설정했습니다.
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer() throws MalformedURLException {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSION");
serializer.setSameSite("None");
serializer.setUseSecureCookie(true);
return serializer;
}
}
CookieSerializer
를 통해 기본적인 쿠키 설정을 해주었습니다.
serializer.setSameSite("None")
를 통해 쿠키 정책을 변경하고자 했고, 이를 위해 serializer.setUseSecureCookie(true)
를 통해 쿠키를 secure
로 설정하고자 했습니다.
그런데 여기서 HTTPS
가 아닌 HTTP
로 통신을 진행하고 있어 문제가 발생했습니다.
serializer.setUseSecureCookie(true)
의 경우 HTTPS
에서만 적용될 수 있기 때문에SameSite=None
으로 설정할 수가 없었고, SameSite=Lax
가 적용되어 프론트엔드에서는 쿠키를 전달받을 수 없었습니다.
이전 프로젝트에서는 HTTPS
환경에서 작업했기 때문에 크게 신경쓰지 않았으나 지금 환경에서는 큰 걸림돌이 되었습니다.
이로 인해 다른 방법을 찾아봤으나, 결국 쿠키 방식은 포기하고 Header
에 Session ID
를 넘겨주는 방식으로 변경했습니다.
@Configuration
public class SessionConfig {
@Bean
public HttpSessionIdResolver httpSessionStrategy() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
}
HeaderHttpSessionIdResolver
를 통해 X-Auth-Token
으로 헤더로 전송하고자 했습니다.
그런데 여기서 또 프론트엔드가 해당 헤더에 접근할 수 없는 문제가 발생했습니다.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "36000");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key, X-Auth-Token");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
위와 같이 Access-Control-Allow-Headers
로 X-Auth-Token
에 대한 접근을 허용하고 있다고 생각했기 때문에 계속 방법을 찾기는 했으나 결과가 잘 나오지 않았습니다.
그러던 도중 프론트엔드 개발자분께서 Access-Control-Expose-Headers
를 언급해주셨고, 제가 Access-Control-Allow-Headers
에 대해 착각하고 있다는 것을 깨닫게 되었습니다.
Access-Control-Allow-Headers
의 경우 preflight
에 관련된 설정이였으며, Access-Control-Expose-Headers
가 프론트엔드에서 접근할 수 있는 헤더의 종류를 허용해주는 역할이였습니다.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "36000");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key, X-Auth-Token");
response.setHeader("Access-Control-Expose-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key, X-Auth-Token");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
그래서 위와 같이 Access-Control-Expose-Headers
를 추가해 해결할 수 있었습니다.
프론트엔드에서 헤더의 이름을 변경해달라는 요청이 들어와, 이를 다음과 같이 처리했습니다.
@Configuration
public class SessionConfig {
@Bean
public HttpSessionIdResolver httpSessionStrategy() {
return new HeaderHttpSessionIdResolver("x_auth_token");
}
}
HeaderHttpSessionIdResolver.xAuthToken()
또한 내부적으로 X-Auth-Token
이라는 상수를 통해 HeaderHttpSessionIdResolver
를 생성하고 있었기 때문에 이 정도면 충분하다고 판단했고, 로컬에서 테스트 시 정상적으로 동작함을 확인할 수 있었습니다.
이후 배포 후 배포 환경에서 테스트를 해보자, Session
을 찾지 못하고 401
을 반환하는 오류를 확인했습니다.
이에 대해서 여러 시도를 해봤으나, 원인조차 찾지 못해 결국 기본 값인 HeaderHttpSessionIdResolver.xAuthToken()
을 사용하는 것으로 롤백하게 되었습니다.
일반 로그인 시에는 정상적으로 동작하나, 소셜 로그인 시에는 CORS
가 발생하는 현상이 발견되었습니다.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter("/*")
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "36000");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key");
response.setHeader("Access-Control-Expose-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
이를 해결하기 위해 @WebFilter("/*")
를 통해 Spring Security OAuth2 Client
에서 자동으로 처리해주는 요청을 포함한 모든 요청이 Cors
설정 필터를 거치도록 설정해주었습니다.