이번 주는 메인 페이지의 지도를 커스텀 했다. 우리는 kakao map을 사용하기로 했다. 공식문서의 정리가 잘 돼 있고 이미 사용하신 분들의 참고자료들도 풍부했다. 그래서 사용을 하게 되었다. 특히나 커스텀의 범위가 깊었는데, 클러스터부터 마커까지 왠만한 커스텀은 다 됐다. 오늘은 kakao map을 사용을 하면서 겪은 어려운 점 및 어떻게 해결을 했는지에 대해서 정리를 해보려고 한다.
나는 리액트 환경에서 사용을 하는 거였고, 공식 문서에서는 기본적인 html에서의 사용법이 소개되어 있었다. 그래서 그걸 바탕으로 리액트 환경에서 적용을 하고 있었다. 클러스터 안에 마커를 넣고, 마커에 이벤트를 추가하려고 했다. 마커를 클릭했을 때 마커의 배경색이 바뀌길 원했고, 또 해당 마커 지점의 기상 정보들이 정보창에 뜨길 원했다. 그걸 위해서 state를 사용하려고 했다.
하지만 배열에 든 많은 마커를 표현하는 태그에 내가 넣고 싶은 state와 이벤트를 넣고 map메소드를 작동하니 undefined가 화면에 출력되었다. 그래서 console.log를 해보니까 '$$typeof'라는 이상한 태그가 출력이 되었다. 이 문제를 찾아보면서 다음 두 블로그를 찾았다. 특히나 두 번째 블로그에서 많은 도움을 얻었고 이번 글의 상당한 부분은 그곳에서 나온 정보라는 것을 미리 알려둔다. 첫 번째 블로그를 스키밍 하다가 두 번째 블로그를 들어가게 되었는데, 리덕스를 만든 댄 아브라모프(Dan Abramov, 이하 Dan으로 부르겠음. 친구는 아니지만) 블로그여서 굉장히 신기했다.
Reading JSX as if it were JavaScript
Why Do React Elements Have a $$typeof Property?
리액트에서 우리가 평소에 작성을 하던 JSX 문법을 작성하는 것은 함수를 부르는 것이다. 그 함수는 리액트 엘리먼트를 반환해준다. 아래의 예시 코드를 보도록 하자.
// 다음과 같은 jsx를 입력을 하면
<marquee bgcolor="#ffa7c4">hi</marquee>
// 다음과 같은 함수가 호출이 되는 것이다.
React.createElement(
/* type */ 'marquee',
/* props */ { bgcolor: '#ffa7c4' },
/* children */ 'hi'
)
// 이 함수는 다음과 같은 엘리먼트를 반환해준다.
{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'), // 🧐 Who dis
}
리액트는 결국 이런 과정으로 element들을 가지게 된다. 리액트는 왜 이런 형태를 가질까? 마지막에 있는 $$typeof는 뭘까?
만약에 아래와 같이 JSX를 작성해본다고 하자.
<p>
{message.text}
</p>
그런데 누군가가 'message.text'에 개발자가 의도치 않은 노드 태그처럼 악의적인 코드를 넣는다고 가정한다면, 그리고 리액트가 그것을 바로 렌더링을 해버린다고 한다면, 그것은 보안상 큰 문제가 될 것이다. 이를 막기 위해서 우리는 Dan이 블로그에서 safe APIs라고 소개한 document.createTextNode() 혹은 textContent를 사용할 수 있다. 또 태그를 나타내는 <,> 괄호들을 미리 제거하는 방법도 사용해서 이런 문제들을 예방할 수 있다. 하지만 매번 모든 경우에 이 처리를 해줘야 하는 것은 번거롭기도 하고 놓쳤을 때의 위험도도 존재한다. 이러한 이유 때문에 React에서는 문자열 텍스트에 대한 사전 처리가 기본적으로 지원되고 있다. 그래서 위의 message.text에 악의적인 태그가 들어온다면 React는 처리를 거부한다.
임의의 HTML을 React element로 렌더링 해야 한다면 Dan은 dangerouslySetInnerHTML = {{__html: message.text}}를 사용하라고 한다.
To render arbitrary HTML inside a React element, you have to write dangerouslySetInnerHTML={{ __html: message.text }}. The fact that it’s clumsy to write is a feature. It’s meant to be highly visible so that you can catch it in code reviews and codebase audits.
하지만 이런 방법들이 외부 공격에 대한 완전한 대책이냐를 묻는다면 그것은 또 아니라는 것이 Dan의 설명이다. 왜냐하면 나머지 공격의 대부분의 방향은 attributes에 있다고 한다. Dan은 attributes를 통한 외부 공격 예시를 소개했다.
For example, if you render <... href={user.website}>, beware of the user whose website is 'javascript: stealYourPassword()'. Spreading user input like <div {...userData}> is rare but also dangerous.
또한 리액트가 발전하면서 더 많은 protect를 제공할 수 있지만 그럼에도 이러한 문제들의 대부분은 서버에서 생긴 문제들이라고 한다. 특히나 서버에서 사용자들이 임의의 json을 저장할 수 있게 된다면 이는 큰 문제가 될 수 있다. 이런 경우 React0.13 버전까지는 XSS 공격에 취약할 수 있다.
However, if your server has a hole that lets the user store an arbitrary JSON object while the client code expects a string, this could become a problem:
이런 상황에서 나온 것이 $$typeof라고 한다. 리액트0.14에서는 모든 React element에 'Symbol'태그를 달면서 문제를 해결했다.
기본적으로 JSON에 Symbol을 넣을 수 없다. 그래서 서버에서 보안적인 결함이 있어서 text 대신 JSON을 반환한다고 해도, 그 JSON은 Symbol.for('react.element')을 포함할 수 없게 되는 것이다. 또 동시에 리액트는 element.$$typeof를 체크한다. 그리고 만약 키가 빠진 부분이 있거나 유효하지 않은 부분이 있다면 그 element의 처리를 거부한다.
위와 같은 문제로 인해서 내가 외부 변수에 태그를 작성해서 리액트에 렌더링을 시도했을 때 잘 안됐던 것이었다. 결국 내가 외부에 작성한 마커 태그도 Dan의 블로그에서 표현한 임시적인 HTML이었기 때문에 렌더링이 거부되었던 것이다. 그렇다고 모든 태그들을 Dan이 알려준 방법대로 처리하기란 너무 번거로웠다. 그래서 찾다보니 React에서 사용할 수 있도록 kakao map을 리팩토링을 해둔 SDK를 발견하게 되었다. 김재서 프로그래머님 감사합니다
김재서 님께서 만들어주신 이 라이브러리를 이용하면 우리가 일반적으로 리액트를 사용하는 것처럼 kakao map을 사용할 수 있었다. kakao map의 각 기능들을 component처럼 Import해서 쓸 수 있고 그렇기 때문에 styled-components에 extends해서 쓸 수도 있다. 덕분에 매우매우 편하게 kakao map을 빌드할 수 있었고 위에서 내가 겪은 state를 tag에 props로 binding하는 문제나 tag에 event를 binding하는 문제도 리액트 방식 그대로 처리할 수 있었다.
이번 기회를 통해서 알면 좋지만 모른다고 해서 문제가 크게 되지 않는 문제를 공부해 볼 수 있게 되었다. 정말로 Dan도 블로그에서 '리액트를 사용하기에 필요하진 않지만 너가 알게 되었을 때 기분 좋을 거 같은 것 중 하나'라고 했는데, 딱 그런 거 같다. 물론 이번에 공부하면서 읽게 된 Dan의 블로그도 모두 영어이고 내가 영어를 잘하진 않아서 100% 이해가 된 건 아니지만, 최근 들어서 내가 적는 코드가 혹시나 보안상 문제가 되진 않을지 걱정을 하는데 특히나 그런 걱정을 하던 찰나에 이런 공부를 하게 되어 더 기분이 좋다.
갓갓 벨로그에는 이미 해당 블로그에 대한 글을 번역하신 분이 계셨다! 다 쓰고 내 글과 연관된 글에서 발견하게 된 이 벨로그는 매우 정확하게 번역을 해두었으니 읽어볼 분들은 참고하시길. scamera.log
참고자료
Reading JSX as if it were JavaScript
Why Do React Elements Have a $$typeof Property?
JSX 없이 사용하는 React
오 리액트는 xss 대비를 이런 방식으로 해놓았군요. 역시 인기에는 이유가 있네요!