ColdFusion에서는 오래전부터 태그를 지원하기 전부터 Java를 응용하여 만들어진 많은 Captcha 소스가 있었습니다. 대부분 쉽게 가져다 쓸 수 있도록 CFC(콜드퓨전컴포넌트)나 UDF(사용자정의 함수) 또는 Custom Tag(사용자정의 태그)로 제공되고 있어서 매우 쉽게 쓸 수 있었습니다.
그러나 Adobe ColdFusion이란 이름으로 Adobe에 인수되어 첫 출시된 버전 8에서는 쉽게 이미지를 핸들링 할 수 있는 태그를 지원하면서 Captcha도 동시에 쉽게 구현되도록 지원되기 시작했습니다. 이후 Railo나 OpenBD도 모두 지원하기 시작했습니다. 따라서 대부분의 ColdFusion서버에는 다음과 같은 단 한줄의 태그로 이미지로 만들어지는 Captcha를 생성합니다.
<cfimage action="captcha" difficulty="high" fontSize="25" width="300" height="50" text="Captcha" fonts="Arial" />
물론, Adobe ColdFusion 10 이상의 버전에서는 ImageCreateCaptcha()란 함수도 지원하여 또다른 코드방식으로 생성할 수 있습니다만 이 역시 단 2줄이면 Captcha의 생성은 끝납니다. ColdFusion 언어의 특성상 HTML의 태그와 유사하여 Captcha를 생성하더라도 간단한 속성의 정의로 가로세로의 크기, 서체, 서체의 크기, Captcha의 난이도를 변경할 수 있습니다. 한국인을 위한 전용 웹사이트라면 한글서체를 이용해서 한글 Captcha도 만들 수 있습니다. (단 한줄의 코드로)
<cfset funcCaptcha = ImageCreateCaptcha(50, 300, "Captcha", "medium", "serif,sansserif", "25") />
<cfimage action="writetoBrowser" source="#funcCaptcha#" />
위 Tag로 생성한 경우나 함수로 생성한 경우나 개발자가 신경 쓸 부분은 Captcha의 내용 즉 문자를 랜덤으로 조합하여 보여줄 것만 고민하면 됩니다. 아래의 예제에서 문자나 숫자범위에서 랜덤하게 추출하는 것은 코드로 살펴보도록 하겠습니다.
자, 말을 좀 바꾸어서, 그런데 Captcha를 쓰는 가장 큰 이유는 스팸과 같은 자동등록되는 Bot에 의한 게시글 공격을 막기 위함인데 Captcha의 특성이 웹 접근성과 완전히 위배된다는 것에서 괴리가 생깁니다. 웹 접근성적인 관점에서는 모든 이미지는 해당 이미지의 내용과 특성을 알 수 있도록 HTML의 태그의 alt 속성과 같은 기능을 이용해서 시각장애인이 쉽게 인지할 수 있도록 구현해야 합니다. (백남중님의 Captcha에 대한 단상 참조)
그런데, 웹 접근성 관점에서 Captcha를 읽을 수 있게 만든다고 HTML소스코드내에 Captcha의 내용을 텍스트로 제공하면 Captcha를 쓰는 이유가 사라지게 됩니다. 바로 Bot 들이 인지할 수 있기 때문이죠. Captcha 자체의 이미지도 OCR같은 기능을 이용하면 쉽게 텍스트를 탐지할 수 있는데 말이죠.
그래서 구글의 reCaptcha 같은 프로젝트에서는 시각장애인을 위하여 Captcha의 문자를 소리로 들려주기도 합니다. 해외에서 제공되는 대부분의 Captcha관련 API는 음성을 지원하여 간단하게 가져다 쓸 수 있지만(물론 ColdFusion에서도 이용할 수 있도록 ColdFusion reCAPTCHA tag와 같은 오픈소스 프로젝트도 있습니다.) reCaptcha의 소리를 들어보시면 알겠지만 이게 도대체 무슨 소리인지 인지불가이거나 영어를 읽어주므로 영어발음을 잘 알아 듣지 못하면 이용할 수 없게 됩니다.
더욱이 외국인들도 G와 Z의 발음은 대부분 "쥐~"와 같이 발음되어 혼동할 수 있어 Captcha의 문자를 읽어줄때면 군대에서 읽듯 A는 "에이"라고 발음하지 않고 "알파"라고 발음합니다. ABC라는 문자열이라면 "에이비씨"가 아니라 "알파브라보찰리"와 같이 읽어 준다는 것이죠. 따라서 이런 내용을 모르는 사람이라면 이용이 불가능하겠죠?
즉, reCaptcha를 국내 웹사이트에서도 많이들 가져다 쓰지만 오히려 접근성은 왕창 떨어지는 결과를 초래합니다. 국내의 오픈소스 게시판중 유명한 "그누보드"가 웹 접근성이 적용된 버전을 내놓았다고 했을 때 과연 Captcha는 어떻게 구현했을까? 궁금했습니다. 그전에도 Captcha는 지원하고 있었고 단순하지만 숫자 5~6개를 입력하는 Captcha를 지원하고 있던터라 더더욱 궁금했습니다.
결론적으로 말하자면 그누보드에서는 Captcha에서 제공하는 숫자를 이미지 Captcha로 여전히 지원하면서 다만 각각의 숫자에 해당되는 mp3파일을 동적으로 로드해서 하나의 mp3로 합친 후 웹사이트에서 재생해 주는 구조로 되어 있습니다. 간단하지만 아주 웹 접근성을 잘 이해하고 준수하고 있다고 생각합니다.
참고 : 음성 캡챠와 관련하여서는 https://captcha.com/audio-captcha-examples.html 를 참조하시도 도움 되실 겁니다.
그렇다면 ColdFusion에서는 어떻게 Captcha의 내용에 해당되는 mp3파일(또는 다른 포맷의 음성파일)을 동적으로 합성하여 서비스 할 수 있을까요? 최근 제가 다니는 회사에서 ColdFusion으로 개발중인 한 지역 방송국의 홈페이지에서 웹 접근성을 준수한 Captcha의 구현이 반드시 필요 했습니다. 일단 컨셉은 그누보드와 유사하게 만들자였는데 문제는 mp3와 같은 Binary 파일을 합치는 방법에 대해 고민을 했죠. PHP의 경우에서는 비교적 간단하게 구현되었지만 ColdFusion은 Java로 작성되고 서비스되는 Application server이므로 Java를 응용하면 된다는 것 이였지만 생소한 방법이라 구현에 대한 팁을 얻기 힘들었습니다만 해외의 경우 ColdFusion 사용비율이 높기 때문에 몇번의 구글링으로 "이미지파일(Binary)을 자르고 합치는" 예제를 얻을 수 있었습니다. 이를 응용하여 아래와 같은 웹 접근성을 준수하는 Captcha를 만들 수 있었습니다.
아래의 코드는 새로 고침을 할 때마다 임의의 숫자가 나열 됩니다. 숫자는 0부터 9까지의 숫자를 5개를 골라서 랜덤하게 출력됩니다. 물론 각각의 숫자에 해당되는 mp3파일을 바이너리로 읽어와 동적으로 합성하여 하나의 파일로 만들어 임시파일을 만들게 됩니다. Captcha코드나 이 임시파일명을 Session을 만들어 전송한 후 유효성이 인정되면 임시파일은 삭제하는 코드를 만들어 사용해야 겠죠? 물론 접속자마다 Unique하게 생성되므로 사용자가 브라우저를 그냥 닫아버리거나 한 경우 남아있는 임시파일을 지우는 코드를 별도로 만들고 ColdFusion의 스케줄러를 이용해서 주기적으로 삭제하는 것도 서버공간을 낭비하지 않는 방법일 수 있습니다.(그누보드에서는 그냥 다 남겨 두더군요. ㅠㅠ)
몇십줄 안되는 코드지만 대부분 Captcha에서 필요한것은 구현되어 있습니다. 각각의 소스코드내에 주석을 달아 두었으므로 살펴보시길 바랍니다. 가장 핵심은 Captcha의 랜덤생성 부분(4~18번째 줄까지)과 Mp3의 로드 및 합성 부분(19~53번째줄)입니다.
<cfprocessingdirective pageencoding="utf-8" />
<cfcontent type="text/html; charset=utf-8">
<!--- CAPTCHA용 문자열. --->
<cfset strList = "0,1,2,3,4,5,6,7,8,9" />
<!--- captchaLetter 구조체 생성 --->
<cfset captchaLetter = StructNew() />
<!--- 5개만 랜덤하게 추출 --->
<cfloop condition="(StructCount(captchaLetter) LT 5)">
<cfset intIndex = RandRange(1, ListLen(strList)) />
<cfset captchaLetter[ListGetAt(strList, intIndex)] = true />
</cfloop>
<!--- 추출된 5개의 숫자 --->
<cfset captchaStr = #structKeyArray(captchaLetter)# />
<!--- mp3파일 바이너리 길이를 담을 임시 변수 ---->
<cfset audioArray = 0>
<!--- CAPTCHA 문자의 숫자 만큼 루푸 --->
<cfloop from = "1" to = "#arrayLen(captchaStr)#" index = "i">
<!--- captchaStr 문자열에 해당하는 mp3 파일 로드 --->
<cffile action = "readBinary" file = "#expandPath('./sound/#captchaStr[i]#.mp3')#" variable = "audio" />
<!--- 각 mp3의 배열길이를 하나로 합침 ---->
<cfset audioArray = audioArray + ArrayLen(audio) />
</cfloop>
<!--- java.nio.ByteBuffer 오브젝트 생성 --->
<cfset objByteBuffer = CreateObject("java", "java.nio.ByteBuffer") />
<!--- 각각의 mp3의 배열 길이의 합계만큼 먼저 메모리상에 Allocate 함. 즉, 모든 mp3의 배열길이의 합 --->
<cfset audiofull = objByteBuffer.Allocate(JavaCast("int", (audioArray))) />
<!--- 각각의 mp3를 가져와 합치는 루프 ---->
<cfloop from = "1" to = "#arrayLen(captchaStr)#" index = "i">
<cffile action = "readBinary" file = "#expandPath('./sound/#captchaStr[i]#.mp3')#" variable = "audio" />
<!--- audiofull 변수에 각각의 mp3 배열길이만큼 Put 함 --->
<cfset audiofull.Put(audio, JavaCast("int", 0), JavaCast("int", ArrayLen(audio))) />
</cfloop>
<!---파일명 임의의 문자열 생성 --->
<cfset tempMP3 = "#createUUID()#" >
<!--- audiofull 값을 mp3파일로 저장 --->
<cffile action="write" file="#expandPath('./temp/#tempMP3#.mp3')#" output="#audiofull.Array()#" mode="777" />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CAPTCHA DEMO</title>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script>
$(function() {
//그누보드 CAPTCHA 미리듣기 jQuery 인용
$("#captcha_mp3").click(function(){
var mp3_url = this.href;
var html5use = false;
var html5audio = document.createElement("audio");
if (html5audio.canPlayType && html5audio.canPlayType("audio/mpeg")) {
var wav = new Audio(mp3_url);
wav.id = "mp3_audio";
wav.autoplay = true;
wav.controls = false;
wav.autobuffer = false;
wav.loop = false;
if ($("#mp3_audio").length) $("#mp3_audio").remove();
$("#captcha_mp3").after(wav);
html5use = true;
}
if (!html5use) {
var object = '<embed id="mp3_embed" src="' + mp3_url + '" autoplay="true" hidden="true" volume="100" type="audio/x-wav" style="display:inline;" />';
if ($("#mp3_object").length) $("#mp3_object").remove();
$("#captcha_mp3").after(object);
}
return false;
});
});
</script>
</head>
<body>
<!--- CAPTCHA 이미지 생성 ---->
<cfimage action="captcha" difficulty="medium" fontSize="25" width="220" height="50" text="#REReplace(ArrayToList(captchaStr, ","), ",", "", "ALL")#" fonts="Arial,Courier New,Courier" />
<a href="./temp/<cfoutput>#tempMP3#</cfoutput>.mp3" id="captcha_mp3" target="_blank" title="인증코드 듣기"><img src="./images/sound.png" border="0" alt="인증코드 듣기"></a>
</body>
</html>
ColdFusion의 은 파일을 핸들링할 수 있는 태그인데, 특정 파일을 바이너리형태로 읽어 들여 배열로 저장할 수 있습니다. 물론 여러 파일을 동시에 읽어들여 배열화 할 수 있는데 각각의 파일의 배열의 길이를 구하여 그 길이만큼의 빈 공간에 하나씩 더해가서 저장하는 반복으로 여러 바이너리파일을 순차적으로 합성합니다. (이 부분은 Java를 이용) Mp3 뿐만 아니라 이미지, 일반 문서파일의 분할과 결합을 할 수 있는 것이죠. 어렵지 않은 개념이지만 ColdFusion으로 구현하다보니 조금 생소했습니다. 이것 때문에 Facebook의 ColdFusion Developer 그룹에 물어봤는데 도움은 못되었지만 제가 참고한 소스를 그곳에도 남겨두었습니다. 웹 접근성을 준수해야 하는 최근의 웹사이트 구축환경에서 다양한 언어로 비슷하게 구현해 볼 수 있을 것 입니다.
'ColdFusion' 카테고리의 다른 글
Adobe ColdFusion Pre-Release Program Closure (0) | 2021.04.17 |
---|---|
ColdFusion에서의 QR코드 생성, 그리고 Image Masking (0) | 2019.11.29 |
Lucee Server에서 PDF생성 시 한글 폰트(CJK custom font) 사용하기 (0) | 2019.09.23 |
Lucee Server에서 Remote address가 127.0.0.1로만 반환될 때 (2) | 2019.09.03 |
JSP와 ColdFusion의 Session 공유 (0) | 2019.08.26 |
CFFTP 연결 후 디렉토리 및 파일명의 한글이 깨져 보일 때 (0) | 2019.08.25 |
Adobe ColdFusion Express Edition 다운로드 (0) | 2019.08.25 |
ColdFusion에서 Garbage Collection하기 (0) | 2019.08.25 |