팀 프로젝트 진행 중 회원관리와 관련된 페이지를 제작하며 무분별한 인증을 하지 않도록 막는 작업이 필요했다.
예를 들어, 계정 찾기 페이지에서 유저가 입력한 휴대폰 번호로 인증 번호를 보내는 작업을 하게 되는데 이때 악성 유저나 봇의 경우 과도하게 요청을 보낼 수 있기 때문이다.
따라서 봇인지 판별하고 제한을 두기 위해 캡차(CAPTCHA)를 도입하기로 했다.
reCAPTCHA는 구글에서 제공하는 캡차이며 내가 만든 사이트에서 일어나는 사기행각, 스팸, 어뷰징 등을 감지해 이를 막을 수 있도록 도와준다.
reCAPTCHA를 모르는 사람들도 사진을 보면 아! 이거 많이 봤지 할 것이다.
로봇이 아닙니다 체크박스나 신호등 사진을 선택하세요 같은 이미지 캡차도 모두 reCAPTCHA의 한 종류이다.
이렇게 많은 사람들한테 익숙하다는 장점도 있고 가이드도 나름 잘 되어있기 때문에 reCAPTCHA를 선택했다.
현재 reCAPTCHA는 v2와 v3의 두 가지 버전을 제공한다.
먼저 사람들한테 익숙한 버전은 v2일 것이다.
v2는 또 두 가지 버전으로 나뉘는데 checkbox와 invisible이다.
checkbox는 "로봇이 아닙니다." 체크박스로 이루어져 있고 invisible은 작은 reCAPTCHA 뱃지가 존재한다.
아래에 reCAPTCHA 뱃지가 들어가고 마우스를 올리면 보호중이라는 안내 문구를 볼 수 있다.
그러다 랜덤으로 위와 같이 이미지 캡차가 뜬다.
v3는 v2와 다르게 체크박스나 이미지 캡차를 사용하지 않는다.
대신 유저의 행동을 관찰하고 점수를 매겨 비정상적인 활동이 감지되었는지를 알려준다.
프로젝트에서 기획 시 사용하고자 한 캡차는 reCAPTCHA v2 invisible이었다.
우선, 비정상적인 활동을 감지하는 캡차의 기능을 살리면서 이미지 캡차를 사용해 약간의 불편함을 감수하는 대신 무분별한 요청을 확실히 방지하고자 했기 때문이다.
하지만 v2는 2014년, v3는 2018년에 공개된 라이브러리이기 때문에 현재까지도 관리되고 있는 v3를 이용하는 것이 더 좋지 않을까 하는 생각도 있었다.
기획 상 원하는 작업은 v2, 라이브러리의 안정성은 v3에 있었기에 둘의 장단점이 극명해 쉽게 선택하지 못했고 결국 둘 다 구현해보고 팀원끼리의 UX를 확인 후 하나를 선택하고자 했다.
++ 22.08.10 업데이트
v2로 결정했다. 라이브러리 안정성을 고려하면 v3를 고르는 것이 옳은 선택이지만 v3를 사용하기에는 v3 이슈가 너무 큰 단점이었다. (거의 대부분 0.9의 점수가 나옴)
찾아보니 나처럼 위의 이슈때문에 v3 대신 v2를 사용하는 케이스가 많은 것 같다.
reCAPTCHA v2 invisible의 구현은 react-google-recaptcha를 사용했다.
구현하기 전, reCAPTCHA admin console에서 사용하고자 하는 버전의 reCAPTCHA 서비스를 등록하고 site key와 secret key를 발급받는다.
import ReCAPTCHA from "react-google-recaptcha";
const recaptchaRef = React.createRef();
ReactDOM.render(
<form onSubmit={() => { recaptchaRef.current.execute(); }}>
<ReCAPTCHA
ref={recaptchaRef}
size="invisible"
sitekey="Your client site key"
onChange={onChange}
/>
</form>,
document.body
);
ReCAPTCHA 컴포넌트에 발급받은 site key를 넣어주고 token을 발급받아 해당 token으로 비정상적인 활동인지 판별한다.
reCAPTCHA v3의 구현은 react-google-recaptcha-v3를 사용했다.
import {
GoogleReCaptchaProvider,
GoogleReCaptcha
} from 'react-google-recaptcha-v3';
const MyComponent: FC = () => {
const [token, setToken] = useState();
const onVerify = useCallback((token) => {
setToken(token);
});
return (
<GoogleReCaptchaProvider reCaptchaKey="[Your recaptcha key]">
<GoogleReCaptcha onVerify={onVerify} />
</GoogleReCaptchaProvider>
);
};
v2와 다른 점은 GoogleReCaptchaProvider
와 GoogleReCaptcha
로 이루어진다는 것이다.
token 판별에 대한 응답 내용만 살짝 달라질 뿐 그 외 token을 받고 token으로 판별하는 로직은 동일하다.
문제는 token을 발급받은 후 token을 판별하기 위해 구글에 요청을 보내는 데에서 생겼다.
token 발급 후 판별하기 위한 로직은 다음과 같다.
1. token을 백엔드로 보낸다.
2. 백엔드에서는 secret key와 token을 구글에 보낸다.
3. 구글에서 판별 결과를 가져와 다시 프론트로 보낸다.
여기서 나는 백엔드로 token을 보내 백엔드에서 구글에 요청을 보내는 이유를 "secret key를 감추기 위해서"라고 생각했다.
프로젝트는 Next.js를 사용해 구현중이기 때문에 요청을 보낼 때 secret key를 감출 수 있어 front에서 진행해도 상관이 없을 것이라 판단하고 front에서 바로 요청을 보내보았다.
그랬더니 위와 같은 에러가 발생했다.
에러를 확인하기 위해 개발자 도구의 Network 탭을 확인해보았다.
그랬더니 요청은 실패했다는데 status code
는 200
이 넘어왔다.
따로 온 response도 없었고 알 수 있는 에러 메세지는 Failed to fetch 하나였기에 원인을 찾기 어려웠다.
약간의 삽질 끝에 문제가 발생한 원인은 개발자 도구의 Network 탭에서 확인할 수 있었다.
항상 요청에 대한 자세한 내용을 확인하느라 Header, Payload 등의 탭을 켜놔서 찾는 것이 늦었는데 이 창을 끄고 나니 CORS error라고 원인이 적혀있는 것을 확인할 수 있었다.
결국 정석대로 백엔드에 token을 보내 백엔드에서 구글에 요청을 보내는 것으로 해결할 수 있었다.
reCAPTCHA v2 invisible의 이미지 캡차 모달이 떴을 때 위 사진과 같이 일부분이 잘리는 현상이 발생했다.
일부분이라고 하더라도 제일 중요한 하단부의 확인 버튼을 누를 수 없었기에 큰 문제였다.
이를 해결하기 위해 reCAPTCHA 컴포넌트에 스타일링을 해봤지만 해결할 수 없었다.
또 reCAPTCHA 컴포넌트를 자식으로 두는 부모 컴포넌트를 생성해 부모 컴포넌트에 스타일링을 했을 때는 변경은 있었지만 원하는 결과를 얻을 수는 없었다.
위 사진이 부모 컴포넌트에서 scale을 조정한 결과이다.
조금 작아지면 모든 내용을 화면에 표시할 수 있을 줄 알았지만 그냥 그대로 작아졌다.
reCAPTCHA의 깃허브에서 같은 내용의 이슈를 확인할 수 있었다.
댓글에는 거의 모두 "Have the same issue!"를 외치고 있다.
그리고 이 페이지의 제일 마지막 댓글에서 이 문제의 원인을 찾을 수 있었다.
For me, this issue seems to only occur when I start the page in full screen, and then resize it to mobile with the chrome devtools device toolbar.
If I start the page in mobile, the image selection is displayed properly and is centered.
So, to me, it's a non-issue after all.
나는 이 문제가 chrome devtools device toolbar에서 모바일 크기로 resize 했을 때만 발생해.
모바일 환경에서 이 페이지를 보면 이미지 선택 캡차가 가운데에 제대로 표시돼.
그래서 나한텐 문제가 아니야.
나 또한 chrome의 device toolbar로 개발 중이었기 때문에 resize되면서 이러한 문제가 발생하는 것이 아닐까 생각했다.
chrome device toolbar의 문제인지 확인하기 위해서 지금까지의 구현된 내용을 배포해보기로 했다.
놀랍게도 진짜로 실제 모바일 환경에서는 정상적으로 이미지 캡차가 렌더링된 것을 확인할 수 있었다.
나한테 왜 그랬어...
궁금한 질문 해도 되나요??