교차 출처 리소스 공유 CORS(Cross-Origin Resource Sharing)

1. SOP (Same-Origin Policy)

같은 Origin에만 요청을 보낼 수 있게 제한하는 보안 정책
서버가 아닌 브라우저에 비교 로직 구현

1. Origin

https://www.google.com/users?sort=asc&page=1#foo

  • https:// : Protocol
  • www.google.com : Host
  • /users : Path
  • ?sort=asc&page=1 : Query String
  • #foo : fragment

출처(Origin) : Protocol + Host + Port

  • Port : 각 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트가 정해져 있기 때문에 생략 가능
  • https://www.google.com:443과 같이 명시적으로 포함되어 있다면 port까지 일치해야 같은 origin
  • IE는 port 무시(port가 달라도 같은 origin)
기준 : https://www.google.com
같은 출처 : o
다른 출처 : x

https://www.google.com/user		o
https://www.google.com/user?q=123	o
http://www.google.com			x (protocol)
https://www.naver.com			x (Host)

2. 사용 이유

XSS(Cross-Site Scripting) / CSRF(Cross-Site Request Forgery) 공격 방지

XSS

악성코드를 웹사이트에 심어 사용자의 정보를 빼내는 공격 방식

  • 한명의 해커가 웹사이트에 하나의 게시글을 쓸 때 사용자(브라우저)의 쿠키 정보를 빼오는 JAVASCRIPT 코드를 삽입
  • 다른 사용자가 로그인을 하고, 로그인 정보가 쿠키에 저장
  • 해당 해커의 게시글을 클릭시, 악성코드가 자동으로 실행
  • 해커의 웹사이트에 사용자의 정보가 담긴 쿠키가 전송

CSRF

로그인 상태에서 해커의 웹페이지 방문시 사용자의 로그인 데이터 악용

  • 사용자가 은행사이트에 로그인 (www.mybank.com)
  • 새로운 탭을 눌러서 해커가 만든 웹페이지를 방문(www.해커.com)
  • 해커의 웹페이지는 다음과 같이 코드를 넣어서 사용자의 은행 계좌에서 돈을 해커의 계좌로 입금 시키라는 요청
  • 은행 웹서버는 사용자의 브라우저 (chrome)에 저장되어 있는 사용자 인증 정보때문에 정상적인 사용자의 요청이라고 인식

2. CORS(Cross-Origin Resource Sharing)

HTTP통신을 할 때, 다른 출처의 자원을 허용
-> Access-Contorl-Allow-Orgin 헤더의 값 세팅을 통해 브라우저에게 허용된 출처임을 알려준다

CORS를 설정하지 않으면 다른 origin의 자원 공유시 아래와 같은 오류 발생

Spring Boot에서의 CORS 해결

1. CorsFilter

Filter 인터페이스를 implements하여 CorsFilter 생성
필터의 로직을 수행하는 doFilter를 커스텀

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); // Origin(URL)
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. true를 응답받은 클라이언트는 실제 요청 시 서버에서 정의된 규격의 인증값이 담긴 쿠키를 같이 보내야 한다.
        response.setHeader("Access-Control-Allow-Methods","*"); // GET, POST, ...
        response.setHeader("Access-Control-Max-Age", "3600"); // preflight 결과를 원하는 시간만큼 저장
        response.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept, Authorization");
                // 실제 요청시 사용할 수 있는 헤더
                
        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }

    }

    @Override
    public void destroy() {

    }
}

preflight : cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 본 요청 이전 실제 요청이 안전한지에 대한 preflight request 과정을 수행
참고: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

2. Cross Origin 어노테이션 활용

  1. 컨트롤러 클래스 단에서 설정
  2. 매서드 단에서 설정
@RestController
@RequestMapping(value = "/api/threats", produces = "application/json")
@CrossOrigin(origins = "http://front-server.com") // 컨트롤러에서 설정
public class ThreatController {

    private final ThreatService threatService;

    public ThreatController(ThreatService threatService) {
        this.threatService = threatService;
    }

    @GetMapping
    @CrossOrigin(origins = "http://front-server.com") // 메서드에서 설정
    public ResponseEntity<ThreatLogCountResponse> getAllThreatLogs() {
        return ResponseEntity.ok(threatService.getAllThreatLogCount());
    }
}

3. WebConfig 설정

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**") // CORS를 적용할 URL 패턴 정의
        .allowedOrigins("http://localhost:8080");
        // 자원 공유를 허용할 Origin 설정
  }
}

좋은 웹페이지 즐겨찾기