1. Origin / Remote Address
- 클라이언트가 특정 서버에 request를 요청할 때에, 서버는 클라이언트의 IP 주소 (Host 주소, Remote Address)도 수집해야 하지만 제일 먼저 요청이 어디서 들어 왔는지 확인해야 함
- 서버의 도메인 주소가 naver.com인데, 다른 사이트에서 naver.com에게 요청을 보내면 비정상적인 접근으로 판단하여 접근을 차단하기 때문
- 요청을 보낸 근원지를 Origin이라고 하고, 이는 클라이언트의 IP 주소와 다름
- 서버는 실제로 Remote Address 외에도 Origin, Referer (이전 주소), User-Agent, CSRF Token 등 요청이 안전하다는 증거를 입증 가능한 정보들을 받아야 함
- Origin, Referer, User-Agent 등은 공격자가 임의로 조작이 가능하기 때문에, 추가로 필요한 검증용 토큰이 바로 CSRF (Cross-Site Request Forgery) Token

Referer
2. CSRF Defence
- CSRF는 사용자의 인증 상태를 악용하여, 사용자가 의도하지 않은 요청을 보내도록 유도하는 공격
- 피싱 사이트 등을 통해 인증 정보를 이용하여 공격하기 때문에 사용자와 서버 양 측에서는 이 요청이 올바른 요청인지, 악의를 가진 공격성 요청인지 알 수 없음
- 이를 검증하기 위해 토큰(CSRF Token)을 사용
- 토큰을 사용할 시 정상적인 서버에서는 세션을 통해 토큰을 지급하고 검증
- 공격자는 토큰의 값을 알 수 없고, 위조할 수 없음

3. CORS (Cross Origin Resource Sharing)
- CSRF 방어의 정책 때문에, HTTP 프로토콜은 같은 Origin이 아닌 곳의 요청을 기본적으로 전부 차단
- 신뢰할 수 있는 특정 Origin의 요청을 열어주는 것이 바로 CORS (Cross Origin Resource Sharing)

- Origin을 확인하기 위해서 클라이언트는 JS를 통한 요청 시 2번의 요청을 전송
- 맨 먼저 전송하는 요청을 Preflight라고 함
- Preflight에서 내가 전송할 요청의 정보를 알려주고, 서버에서 신뢰할 수 있어 요청을 허용하는 응답이 오면 본 요청을 다시 전송
- Preflight가 거절되면 브라우저에서 에러 출력


4. 적용
- 실제 운영할 Blog에 적용하기 위해 Filter를 사용
- CorsFilter
- Access-Control-Allow-Origin : 서버 측에서 접근을 허용하는 Origin 명시
- Access-Control-Expose-Headers : 서버 측에서 응답에 남길 수 있는 Headers 명시
- Access-Control-Allow-Methods : 서버 측에서 허용할 수 있는 Request Methods
- Preflight는 Options라는 Method를 사용하므로 필수로 포함해야 함
- Access-Control-Max-Age : 서버 측에서 CORS를 허용할 수 있는 시간 (초)
- Access-Control-Allow-Headers : 서버 측에서 요청 받을 수 있는 헤더
- Access-Control-Expose-Headers와 다름
- Access-Control-Allow-Credentials : 서버에서 쿠키에 세션 값을 담는 것을 허용할 지에 대한 여부
@Slf4j
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String origin = request.getHeader("Origin");
log.debug("Origin : " + origin);
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
// response.setHeader("Access-Control-Expose-Headers", "Authorization"); // 응답 해줄 수 있는 헤더
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS"); // OPTIONS : Preflight
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Key, Content-Type, Accept, Authorization"); // 요청 받을 수 있는 헤더
// X-~~~ : Customized Header
response.setHeader("Access-Control-Allow-Credentials", "true"); // 쿠키 세션 값 허용
// Preflight 요청을 허용하고 바로 응답하는 코드
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
- FilterConfig
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*"); // 모든 요청에 적용
registrationBean.setOrder(1); // 필터 순서 설정
return registrationBean;
}
Result
- Cursor의 Live Server (127.0.0.1:5500)를 이용하여 Preflight가 승인되는 지 확인


Share article