: SOP(Same Origin Policy)에 대한 일종의 관용책(?) 요즘 같이 다른 origin을 가진 사이트 간에 request & response 통신이 많은 시대(?)에 맞춰 CORS Policy가 나왔다. 즉, SOP만 있던 시절에는 사실상 같은 origin 이 아니면 브라우저 상에서는 통신이 아예 불가능했지만, cors policy가 등장하여 오히려 그러한 strict 한 policy에 특정 조건만 만족한다면 통신을 허용하도록 해준거라고 볼 수 있다.
: 하지만, 여전히 SOP는 개발자를 귀찮게 하고 있다. 물론 보안상의 문제를 브라우저 단에서 해결해준다는 점에서 매우 감사하지만, 아까 말했듯이 브라우저 단에서(개발자로 치면 프론트엔드 단에서) 다른 브라우저 혹은 open api를 사용할 때 브라우저가 자동으로 cors를 적용해서 block 하는 경우가 많기 때문에 이를 bypass 즉, 우회하는 방법에 대해 알아봐야할 필요성이 있다. 이번 포스팅에서는 그러한 부분에 대해서 알아보고자 한다.
: 채팅 관련해서 cors를 우회하고자 해당 포스팅을 진행하고 있는 것이기에 다른 플랫폼에서는 채팅창에 cross-origin url의 데이터를 받고자 할 때 어떻게 cors를 우회하고 있는지 리서치해봤다.
: Mattermost는 서버단에서 open graph 태그 등의 정보를 추출하여 JSON 형태의 데이터로 가공하여 client로 보내주는 식으로 하고 있다. 그 과정을 살펴보면, 아래와 같다.
Step1 : input에 url을 치고, enter를 누르면(실제 post하면)
{
"id": "8m5j9ndcgtrabrskjyyz9cqyqy",
"create_at": 1657500676690,
"update_at": 1657500676690,
"edit_at": 0,
"delete_at": 0,
"is_pinned": false,
"user_id": "ac46731hybbh9xbe3wqw35pkzw",
"channel_id": "hf4sor57obf8tqmi48kjbasjna",
"root_id": "",
"parent_id": "",
"original_id": "",
"message": "https://www.hankyung.com/international/article/2022070971247",
"type": "",
"props": {
"disable_group_highlight": true
},
"hashtags": "",
"pending_post_id": "ac46731hybbh9xbe3wqw35pkzw:1657500676615",
"reply_count": 0,
"last_reply_at": 0,
"participants": null,
"metadata": {
"embeds": [
{
"type": "opengraph",
"url": "https://www.hankyung.com/international/article/2022070971247",
"data": {
"type": "article",
"url": "https://www.hankyung.com/international/article/2022070971247",
"title": "피습 상황 VIP에 장막쳤어야…'경호 실패' 아베 사망으로 몰았나",
"description": "피습 상황 VIP에 장막쳤어야…'경호 실패' 아베 사망으로 몰았나, 이미나 기자, 국제",
"determiner": "",
"site_name": "한경닷컴",
"locale": "",
"locales_alternate": null,
"images": [
{
"url": "https://img.hankyung.com/photo/202207/01.30575849.1.jpg",
"secure_url": "",
"type": "",
"width": 630,
"height": 351
}
],
"audios": null,
"videos": null
}
}
],
"images": {
"https://img.hankyung.com/photo/202207/01.30575849.1.jpg": {
"width": 630,
"height": 351,
"format": "jpeg",
"frame_count": 0
}
}
}
}
위와 같은 데이터를 response로 받는다. “metadata” 키값을 보면 동영상 관련 메타 데이터를 렌더링하기 위한 정보가 왔음을 볼 수 있다(서버 단에서 open graph 태그를 활용해 데이터를 추출하여 보내는 것이고, 이 서버는 mattermost 자체 서버).
Step2 : 해당 데이터를 받아와서 바로 채팅 화면에 렌더링 해준다.
결과적으로 매터모스트는 자체적으로 백엔드 서버를 구축해놓고 urfurl 메서드를 만들어서 이에 대해 처리 해주는 방식으로 cors 우회를 하고 있다.
: slack 도 mattermost와 유사한 방식을 사용하는데 그 과정을 살펴보면, 아래와 같다.
Step1 : 채팅 input 창에 url을 타이핑하면 onChange 이벤트로 이를 감지하여 바로 api를 보낸다(url 링크인 경우).
Request URL : ‘https://nomadcoders.slack.com/api/chat.unfurlLink?_x_id=20e9b58f-1657459364.433&_x_csid=kUvOG19RNO4&slack_route=T60TDKNJK&_x_version_ts=1657321045&_x_gantry=true&fp=a1(자체 서버 api)’
위와 같은 URL로 POST 요청을 먼저 보내서 아래와 같은 데이터를 받는다.
{
"ok": true,
"attachments": {
"https:\/\/www.youtube.com\/watch?v=-Y9VtoPvtuM": {
"from_url": "https:\/\/www.youtube.com\/watch?v=-Y9VtoPvtuM",
"service_name": "YouTube",
"service_url": "https:\/\/www.youtube.com\/",
"title": "Pink Sweat$ - Honesty [Official Music Video]",
"title_link": "https:\/\/www.youtube.com\/watch?v=-Y9VtoPvtuM",
"author_name": "Pink Sweats",
"author_link": "https:\/\/www.youtube.com\/c\/PinkSweats",
"fallback": "YouTube Video: Pink Sweat$ - Honesty [Official Music Video]",
"thumb_url": "https:\/\/i.ytimg.com\/vi\/-Y9VtoPvtuM\/hqdefault.jpg",
"thumb_width": 480,
"thumb_height": 360,
"video_html": "<iframe width=\"400\" height=\"225\" src=\"https:\/\/www.youtube.com\/embed\/-Y9VtoPvtuM?feature=oembed&autoplay=1&iv_load_policy=3\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen title=\"Pink Sweat$ - Honesty [Official Music Video]\"><\/iframe>",
"video_html_width": 400,
"video_html_height": 225,
"service_icon": "https:\/\/a.slack-edge.com\/80588\/img\/unfurl_icons\/youtube.png",
"id": 1,
"original_url": "https:\/\/www.youtube.com\/watch?v=-Y9VtoPvtuM"
}
}
}
위와 같은 메타 데이터를 받는다. iframe 태그까지 string으로 받아와서 실제 enter를 쳐서 postMessage를 할경우에 필요한 정보를 미리 response로 받되 local storage에 저장한다(redux-persist를 쓰는 것일수도 있다).
Step2 : 실제로 엔터를 쳐서 메시지를 입력 완료하면
Request URL : ‘https://nomadcoders.slack.com/api/chat.postMessage?_x_id=20e9b58f-1657458817.473&_x_csid=t_iJLoBmVkA&slack_route=T60TDKNJK&_x_version_ts=1657321045&_x_gantry=true&fp=a1’ 로 POST 요청을 보낸다(자체 서버 api). 이와 동시에 local storage에 있던 정보를 바탕으로 실제 채팅 화면에 채팅 내용(유튜브 url 링크를 올린 경우 제목이랑 유튜브 마크 등이 렌더링됨)이 렌더링 된다.
Step3 : 이 때, 썸네일 이미지는 약간의 딜레이가 이뤄진 다음에 렌더링이 되는데, step2 api를 호출한 직후에 또다른 api를 날린다. 결과적으로
Request URL : https://slack-imgs.com/?c=1&o1=wi720.he540.si&url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FvSfDUh78RsQ%2Fhqdefault.jpg 이 요청을 통해(GET) 썸네일 이미지를 받아온다. 이 url에서 파라미터 중 url 부분의 ‘https://i.ytimg.com/’ 는 유튜브 썸네일을 받을 수 있는 링크이고, 예상이지만 o1 파라미터로 width, height 정보를 줘서 원하는 사이즈로 크롭시켜서 오는 것 같다. 추가로, 이 api 역시 ‘https://slack-imgs.com/’ 라는 자체 서버를 이용해서 쓰고 있음.
결과적으로 슬렉도 unFurl 이라해서 api를 만들어서 쓰고 있었고, 대략적인 플로우를 정리해보면 먼저, 채팅 내용을 빨리 보여주기 위해 링크를 input에 치기만해도 제목, 썸네일 link, iframe 태그(string) 등을 받아온다(response)로 ⇒ 그 다음에 실제로 enter를 치면 이전에 받아온 정보로 렌더링을 하고, 썸네일을 위한 api를 또 호출해서 이미지를 크롭해서 받아온다 라고 추측할 수 있다.
** 추가적으로, 슬렉은 독특하게 같은 url 링크를 또 올리려고 하면(시간을 정해놓고) 아예 blocking을 해버리는 로직을 만들어놨다.
: 결론적으로 cors를 우회하려면 꽤나 다양한 방법이 있지만, 본인이 하고 있는 프로젝트 상황에 맞춰서 적용하되 대부분 서버를 거치거나 프록시 서버를 거쳐서 해결한다는 점을 알고 넘어가자.