spring에서 css를 동적으로 만들어주기 - CORB 이슈 해결

조갱·2023년 6월 18일
0

이슈 해결

목록 보기
11/15

배경

회사에서 프로젝트를 진행하는 중에, 유저의 설정에 따라 css가 다르게 적용되어야 했다.

css의 템플릿은 정해져있었고, 색상 코드 / 정렬 / image URL 등을 커스터마이징 해서 사용하는 기능이다.

custom css는 팝업 창에 필요하므로, 팝업창을 열 때 css 의 URL을 파라미터로 전달하고, 팝업창에서는 전달받은 css를 link 태그의 href로 설정한다.

해결 방법 후보

이미지 서버에 올려서 정적 파일 링크를 내려주기

  1. 유저가 설정값을 변경할 때마다 css파일을 생성한다.
  2. 생성된 파일을 toast (NHN Cloud)의 오브젝트 스토리지에 업로드한다.
  3. 오브젝트 스토리지에 저장된 정적 파일(css) 주소를 db에 저장한다.
  4. db에 저장된 정적 파일(css) 주소를 불러오는 API를 개발한다.
  5. 팝업창을 호출할 때, API를 먼저 호출하여 응답받은 정적 파일(css) 주소를 파라미터로 전달한다.

API 단에서 동적으로 css 파일 본문을 생성해서 응답하기

  1. 유저가 설정값을 변경할 때마다 설정값을 db에 저장한다.
  2. db에 저장된 정적 파일 설정값을 토대로, css의 본문을 리턴하는 API를 개발한다.
  3. 웹 페이지가 로드될 때, styleSheet의 link URL로 API URL을 전달한다.
    (Toast Cloud를 사용하지 않는다.)

이 중에 후자의 방법을 채택하였는데, 이유는

  • css 템플릿이 변경되면 대응이 빠르다.
    (전자의 방법은 오브젝트 스토리지에 올라가있는 css파일도 전부 수정해야 하기 때문)
  • 어떤 방법이든 API를 1개는 개발해야한다.
  • 파일 업로드보다는 템플릿에 맞추어 응답하는 것이 공수가 덜 든다.
    (그리고 third party를 사용하지 않으므로, 네트워크 이슈도 거의 없다.)

물론 단점도 존재했는데,

  • css 본문을 API로 응답하는 것은 네트워크 리소스의 낭비가 발생할 수 있다.
    -> redis로 캐싱을 했다. (TTL: 1H)
    -> 로컬캐시를 쓰지 않은 것은, 비즈니스 로직 상 MSA환경에서 적합하지 않았다.
    -> css 본문의 크기가 2kb정도라 redis에 저장하기에 적합한 용량이라 생각했다.
    -> 이 팝업창이, 요청이 엄청 많은 페이지는 아니다.

위와 같이 해결을 했다.
물론, 요청이 많아져서 서버에 부하가 생길 경우를 대비하여 전자의 방안으로 확장성도 고려했다.

이슈 내용

chrome 브라우저에서 API 호출하면, 응답이 되지 않았다.
API를 호출 하고, 200 응답을 받았는데도
응답값이 없었다.

원인

교차 출처 읽기 차단(CORB)으로 MIME 유형이 text/plain인 교차 출처 응답(...)이 차단되었습니다.

CORB (Cross-Origin Read Blocking)

CORB의 정의

CORB(Cross-Origin Read Blocking)는 Cross-Origin Resource 공격을 방지하기 위한 클라이언트의 보안 기능이다.
리소스 요청 시 동일 출처 정책(Same-Origin Policy)에 위배되는 상황에서 발생할 수 있다.
(*동일 출처 정책 : A사이트에서는 A사이트에 존재하는 리소스를 사용해야 한다.
즉, A사이트에서 B사이트의 리소스를 요청하는 등, 요청과 응답의 서버가 다르면 안된다.
)

CORB이 발생하는 상황

  • Cross-Origin Resource 요청
    브라우저에서 스크립트 또는 AJAX 요청을 통해 다른 도메인의 리소스 (예: 이미지, 스크립트, CSS 파일)를 가져오려고 할 때 발생할 수 있다. (동일 출처 정책 위배)

  • 잘못된 응답 유형
    서버에서 보내는 응답 헤더 중, X-Content-Type-Options: nosniff가 설정되어 있고, 다른 도메인에서 반환된 응답이 잘못된 유형일 경우 CORB 오류가 발생할 수 있다.
    예를 들어, <img src = "http://example.com/sample.json">과 같이 img 태그의 리소스에 image 형식의 응답이 아닌 경우에 발생할 수 있다.

CORB 발생 시 해결 방법

  • JSONP 사용
    JSONP(JSON with Padding)는 json 형태의 데이터에 대해서, Cross-Origin 요청을 허용하기 위한 대안적인 방법이다.
    JSONP는 스크립트 태그를 사용하여 다른 도메인에서 데이터를 동적으로 로드하는 방식이다.

  • 프록시 서버 설정
    클라이언트는 프록시 서버에 요청을 보내고, 프록시 서버는 원격 서버로부터 데이터를 가져와 클라이언트에게 전달하여 동일 출처 정책 위배를 우회할 수 있다.

  • 리소스의 도메인 변경
    클라이언트와 리소스 서버를 관리하는 주체가 동일하고, 둘의 origin이 다르다면
    리소스 서버의 도메인을 클라이언트와 동일하도록 변경하여 동일 출처 정책을 만족시킬 수 있다.

  • 리소스 유형 변경
    CORB 오류가 발생하는 리소스의 유형을 변경할 수도 있다. 서버 측 Content-Type 응답 헤더를 수정한다.

  • CORS 허용 범위 확장
    서버의 응답 헤더를 Access-Control-Allow-Origin: *로 설정하여 모든 도메인에서의 요청을 허용할 수 있다. (이 방법은 보안 상의 위험이 있으므로 신중하게 사용해야 한다)

  • Cross-Origin 리소스 공유 허용
    서버의 응답 헤더를 Access-Control-Allow-Credentials: true로 설정하여 자격증명이 필요한 요청에 대해 Cross-Origin 리소스 공유를 허용할 수 있다.
    단, 이 경우 CSRF (Cross-site request forgery, 사이트 간 요청 위조) 공격에 매우 취약해져, Access-Control-Allow-Origin: *와 함께 사용할 수 없다.

  • 브라우저 확장 프로그램 사용
    브라우저의 확장 프로그램을 사용하여, 브라우저의 동작을 수정하고 Cross-Origin 제약을 해제하여 CORB 오류를 해결한다.

나의 해결 방법

위에 CORB 오류가 발생하는 상황 2가지에 대해 소개했다.
우선 [위 사진](#이슈 내용) 에서 우리 API의 응답 헤더가 어떻게 내려오는지 한번 살펴보고 오면 좋다.

  • Cross-Origin Resource 요청
    우리는 API를 제공하고 있기 때문에 Access-Control-Allow-Origin: * 헤더가 설정되어있다. 따라서, CORS 이슈는 발생하지 않는다.

  • 잘못된 응답 유형
    위 사진에서, MIME Type이 text/plain 으로 응답되는 것을 볼 수 있다.
    따라서, 서버에서 응답하는 Content-Type을 text/css 로 응답하도록 수정하여 해결했다.

-> Spring의 ResponseBody에 contentType을 지정하여 반환했다.

suspend fun getCustomCss(request: ServerRequest): ServerResponse {
    return withContext({보안상 중략}) {
        // 보안상 중략

        val cssBody = reactiveStringRedisTemplate.opsForValue()
            .get({Redis Key})
            .awaitFirstOrNull() ?: {서비스}.getCustomCss(mallNo)

        ServerResponse.ok().contentType(MediaType.valueOf("text/css;charset=utf-8")).bodyValueAndAwait(cssBody)
    }
}

redis에서 불러오는 것도 handler가 아니라 service단에 넣어야 하나 고민이다..

수정된 모습

Reference
https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy
https://ko.wikipedia.org/wiki/JSONP
https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0
https://velog.io/@logqwerty/CORS#access-control-allow-origin-%EC%99%80-access-control-allow-credentials-true%EB%8A%94-%ED%95%A8%EA%BB%98-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%97%86%EB%8B%A4
Chat GPT

profile
A fast learner.

0개의 댓글