<rdf:RDF
    xmlns:s='http://snipsnap.org/rdf/snip-schema#'
    xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
    xml:base='http://pabrantes.net/blog/rdf'>
    <s:Snip rdf:about='http://pabrantes.net/blog/rdf#start/2007-01-09/1'
         s:name='start/2007-01-09/1'
         s:cUser='pabrantes'
         s:oUser='pabrantes'
         s:mUser='pabrantes'>
        <s:content>1 Snipsnap Developing: Integrating JCaptcha With SnipSnap {anchor:Snipsnap Developing: Integrating JCaptcha With SnipSnap}&#xD;&#xA;Like I said a few days ago I wanted to open once again the user registration on the blog, although I also wanted - or at least try - to block spam bots that crawled on this blog last October. &#xD;&#xA;&#xD;&#xA;My idea was to somehow implement a &quot;write what&apos;s in the image&quot; system, which are better know as {link:__CAPTCHA__|url=http://en.wikipedia.org/wiki/CAPTCHA}, a fancy acronym for Completely Automated Public Turing test to tell Computers and Humans Apart. I was pretty sure such kind of system already existed so I googled for it and found a java implementation called {link:JCAPTCHA|url=http://jcaptcha.sourceforge.net/} (why didn&apos;t I just tried that on the 1st place?).&#xD;&#xA;&#xD;&#xA;I must say that the guys from JCAPTCHA did an excellent job on their documentation, the {link: 5 minute application integration tutorial|url=http://forge.octo.com/jcaptcha/confluence/display/general/5+minutes+application+integration+tutorial} is a simple and comprehensive guide and definitely was due to it that I implemented JCAPTCHA so easily with SnipSnap.&#xD;&#xA;&#xD;&#xA;Keep in mind that my implementation is not the only one and probably not perfect. Discussions about other ways or why I&apos;ve done it this way are always welcomed.&#xD;&#xA;&#xD;&#xA;So shall we get our hands dirty with some code?\\&#xD;&#xA;Before doing anything else you&apos;ll have to download jcaptcha {link:library|url=http://sourceforge.net/project/downloading.php?groupname=jcaptcha&amp;filename=jcaptcha-bin-1.0-RC3.zip&amp;use_mirror=switch} and put it in the snipsnap lib directory!&#xD;&#xA; &#xD;&#xA;The second thing is to create a new java package called ora.snipsnap.JCaptcha. &#xD;&#xA;&#xD;&#xA;In that directory you&apos;ll create two different classes:&#xD;&#xA;&#xD;&#xA;* JCatpchaSingleton&#xD;&#xA;* JCatpchaServlet &#xD;&#xA;&#xD;&#xA;I know it&apos;s discussable to have a servlet here instead of org.snipsnap.net package or create a org.snipsnap.net.jcaptcha package, well there were only two classes and I thought there would be no problem at all if they were together.&#xD;&#xA;&#xD;&#xA;JCaptchaSingleton, is as the name says a {link:singleton|url=http://en.wikipedia.org/wiki/Singleton_pattern} and it&apos;s the Snipsnap entry point for JCaptcha engine core, the code is as simple as the following:&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;import com.octo.captcha.service.image.ImageCaptchaService;&#xD;&#xA;import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;&#xD;&#xA;&#xD;&#xA;public class CaptchaServiceSingleton {&#xD;&#xA;    &#xD;&#xA;    private static ImageCaptchaService instance = null; &#xD;&#xA;  &#xD;&#xA;    private CaptchaServiceSingleton();&#xD;&#xA;&#xD;&#xA;    public static ImageCaptchaService getInstance(){&#xD;&#xA;        if(instance==null) {&#xD;&#xA;         instance = new DefaultManageableImageCaptchaService();&#xD;&#xA;        }&#xD;&#xA;        return instance;&#xD;&#xA;    }&#xD;&#xA;}&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;This is mainly the code given at the JCAPTCHA tutorial with few modifications - the constructor became private so that anyone actually tries to instantiate the class, and the logic of getInstance method. &#xD;&#xA;&#xD;&#xA;The {link:servlet|url=http://en.wikipedia.org/wiki/Java_Servlet} is also pretty simple, here is the code:&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;import com.octo.captcha.service.CaptchaServiceException;&#xD;&#xA;import com.sun.image.codec.jpeg.JPEGCodec;&#xD;&#xA;import com.sun.image.codec.jpeg.JPEGImageEncoder;&#xD;&#xA;&#xD;&#xA;import javax.servlet.ServletConfig;&#xD;&#xA;import javax.servlet.ServletException;&#xD;&#xA;import javax.servlet.ServletOutputStream;&#xD;&#xA;import javax.servlet.http.HttpServlet;&#xD;&#xA;import javax.servlet.http.HttpServletRequest;&#xD;&#xA;import javax.servlet.http.HttpServletResponse;&#xD;&#xA;import java.awt.image.BufferedImage;&#xD;&#xA;import java.io.ByteArrayOutputStream;&#xD;&#xA;import java.io.IOException;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;public class ImageCaptchaServlet extends HttpServlet {&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;    public void init(ServletConfig servletConfig) throws ServletException {&#xD;&#xA;&#xD;&#xA;        super.init(servletConfig);&#xD;&#xA;&#xD;&#xA;    }&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {&#xD;&#xA;        &#xD;&#xA;       byte[] captchaChallengeAsJpeg = null;&#xD;&#xA;       ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();&#xD;&#xA;       try {&#xD;&#xA;        String captchaId = httpServletRequest.getSession().getId();&#xD;&#xA;        BufferedImage challenge =&#xD;&#xA;        JCaptchaSingleton.getInstance().getImageChallengeForID(captchaId,&#xD;&#xA;                            httpServletRequest.getLocale());&#xD;&#xA;        JPEGImageEncoder jpegEncoder =&#xD;&#xA;                    JPEGCodec.createJPEGEncoder(jpegOutputStream);&#xD;&#xA;        jpegEncoder.encode(challenge);&#xD;&#xA;        } catch (IllegalArgumentException e) {&#xD;&#xA;            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);&#xD;&#xA;            return;&#xD;&#xA;        } catch (CaptchaServiceException e) {&#xD;&#xA;            httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);&#xD;&#xA;            return;&#xD;&#xA;        }&#xD;&#xA;&#xD;&#xA;        captchaChallengeAsJpeg = jpegOutputStream.toByteArray();&#xD;&#xA;&#xD;&#xA;        httpServletResponse.setHeader(&quot;Cache-Control&quot;, &quot;no-store&quot;);&#xD;&#xA;        httpServletResponse.setHeader(&quot;Pragma&quot;, &quot;no-cache&quot;);&#xD;&#xA;        httpServletResponse.setDateHeader(&quot;Expires&quot;, 0);&#xD;&#xA;        httpServletResponse.setContentType(&quot;image/jpeg&quot;);&#xD;&#xA;        ServletOutputStream responseOutputStream =&#xD;&#xA;                httpServletResponse.getOutputStream();&#xD;&#xA;        responseOutputStream.write(captchaChallengeAsJpeg);&#xD;&#xA;        responseOutputStream.flush();&#xD;&#xA;        responseOutputStream.close();&#xD;&#xA;    }&#xD;&#xA;}&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;The servlet has no code changes from the tutorial, so don&apos;t credit me in any way for it&apos;s code.&#xD;&#xA;&#xD;&#xA;Now we have to change the registration process. Such process is in the __NewUserServlet__ class, located in the org.snipsnap.net package.&#xD;&#xA;&#xD;&#xA;First we had a new error string, regarding a mismatch in the word entered and image.&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;private final static String ERR_WRONG_IMAGE = &quot;login.wrong.image&quot;;&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;Now add a private method within the class which will be responsible to tell if the written word is the same as the image or not:&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;private boolean isResponseCorrect(HttpServletRequest request) {&#xD;&#xA;&#9;  String captchaId = request.getSession().getId();&#xD;&#xA;      String response = request.getParameter(&quot;j_captcha_response&quot;);&#xD;&#xA;      boolean isResponseCorrect = false;&#xD;&#xA;       try {&#xD;&#xA;           isResponseCorrect = JCaptchaSingleton.getInstance().validateResponseForID(captchaId,&#xD;&#xA;                   response);&#xD;&#xA;       } catch (CaptchaServiceException e) {&#xD;&#xA;            e.printStackTrace(); &#xD;&#xA;       }&#xD;&#xA;&#xD;&#xA;       return isResponseCorrect;&#xD;&#xA;}&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;Finally in the __doPost__ method find the following code,&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;/* ... */&#xD;&#xA; if (!config.deny(Configuration.APP_PERM_REGISTER)) {&#xD;&#xA;      String login = request.getParameter(&quot;login&quot;);&#xD;&#xA;      String email = request.getParameter(&quot;email&quot;);&#xD;&#xA;      String password = request.getParameter(&quot;password&quot;);&#xD;&#xA;      String password2 = request.getParameter(&quot;password2&quot;);&#xD;&#xA;&#xD;&#xA;      login = login != null ? login : &quot;&quot;;&#xD;&#xA;      email = email != null ? email : &quot;&quot;;&#xD;&#xA; &#xD;&#xA;      if (request.getParameter(&quot;cancel&quot;) == null) {&#xD;&#xA;/* ... */&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;Becomes,&#xD;&#xA;&#xD;&#xA;{code}&#xD;&#xA;/* ... */&#xD;&#xA;if (!config.deny(Configuration.APP_PERM_REGISTER)) {&#xD;&#xA;      String login = request.getParameter(&quot;login&quot;);&#xD;&#xA;      String email = request.getParameter(&quot;email&quot;);&#xD;&#xA;      String password = request.getParameter(&quot;password&quot;);&#xD;&#xA;      String password2 = request.getParameter(&quot;password2&quot;);&#xD;&#xA;&#xD;&#xA;      login = login != null ? login : &quot;&quot;;&#xD;&#xA;      email = email != null ? email : &quot;&quot;;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;      if (request.getParameter(&quot;cancel&quot;) == null) { &#xD;&#xA;             if(!isResponseCorrect(request)) {&#xD;&#xA;    &#9;       errors.put(&quot;login&quot;,ERR_WRONG_IMAGE);&#xD;&#xA;    &#9;       sendError(session, errors, request, response);&#xD;&#xA;    &#9;       return;       &#xD;&#xA;             }&#xD;&#xA;&#xD;&#xA; /* ... */&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;Now that you have the two classes done and edited the servlet that handles registration you __only__ need to take care of the mappings, the jsp, the localization and the build process. It sounds much, but it&apos;s not!&#xD;&#xA;&#xD;&#xA;__Mappings__&#xD;&#xA;&#xD;&#xA;You have to edit the file web.xml-tmpl in the directory ~~src/apps/default/WEB-INF~~ in that file you  have to add the following lines:&#xD;&#xA;&#xD;&#xA;* In the servlets listing&#xD;&#xA;{quote}&#xD;&#xA;    &lt;servlet&gt;&#xD;&#xA;        &lt;servlet-name&gt;jcaptcha&lt;/servlet-name&gt;\\&#xD;&#xA;        &lt;servlet-class&gt;org.snipsnap.jcaptcha.JCaptchaServlet&lt;/servlet-class&gt;\\&#xD;&#xA;        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;\\&#xD;&#xA;    &lt;/servlet&gt;&#xD;&#xA;{quote}&#xD;&#xA;&#xD;&#xA;* In the servlets mappings&#xD;&#xA;{quote}&#xD;&#xA; &lt;servlet-mapping&gt;\\&#xD;&#xA;        &lt;servlet-name&gt;jcaptcha&lt;/servlet-name&gt;\\&#xD;&#xA;        &lt;url-pattern&gt;/jcaptcha&lt;/url-pattern&gt;\\&#xD;&#xA;    &lt;/servlet-mapping&gt;&#xD;&#xA;{quote}&#xD;&#xA;&#xD;&#xA;__JSP__&#xD;&#xA;&#xD;&#xA;You have to edit the register.jsp, which is in the ~~src/apps/default/~~ directory and add the following table line to the existing table:&#xD;&#xA;&#xD;&#xA;{quote}&#xD;&#xA;  &lt;tr &lt;c:if test=&quot;${errors[&apos;letters&apos;] != null}&quot;&gt;class=&quot;error-position&quot;&lt;/c:if&gt;&gt;\\&#xD;&#xA;    &lt;td&gt;\\&#xD;&#xA;    &lt;fmt:message key=&quot;login.letters&quot;/&gt;\\&#xD;&#xA;    &lt;/td&gt;\\&#xD;&#xA;    &lt;td&gt;\\&#xD;&#xA;   &lt;input type=&apos;text&apos; name=&apos;j_captcha_response&apos; value=&apos;&apos;&gt;&lt;/td&gt;\&#xD;&#xA;   &lt;td&gt;&lt;img src=&quot;jcaptcha&quot;&gt;&lt;/td&gt;\\&#xD;&#xA;    &lt;/td&gt;\\&#xD;&#xA;    &lt;/tr&gt;\\&#xD;&#xA;{quote}&#xD;&#xA;&#xD;&#xA;__Localization__&#xD;&#xA;&#xD;&#xA;Access the internationalization directory at ~~src/apps/default/WEB-INF/classes/i18n~~ and edit at least the message resource file that you using, most probably ~~messages_en.properties~~ and enter the value pair &#xD;&#xA;&#xD;&#xA;~~login.letters=Please enter the letters&lt;br/&gt; you see in the image~~&#xD;&#xA;&#xD;&#xA;__Build Process__&#xD;&#xA;&#xD;&#xA;You have to add the jcaptcha library in the following places of the ~~build.xml~~:&#xD;&#xA;&#xD;&#xA;* Add it to __server.classpath__&#xD;&#xA;* Add it to __app.classpath__&#xD;&#xA;* In the __jar-server__ task, include it in the fileset that will generate the jar.&#xD;&#xA;* In the __snipsnap-war__ task, add it to the file list that are being copied into ~~WEB-INF/lib~~ library.&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;__Finishing__&#xD;&#xA;&#xD;&#xA;Now you only need to recompile snipsnap and redeploy it and you&apos;ll see a image coming up on the registration interface. And that will only allow you to register __if__ the letters that have been typed are the same in the image.&#xD;&#xA;&#xD;&#xA;You can see a fine example of integration here in this blog, just check my {link:login|url=https://www.pabrantes.net//blog/exec/login.jsp} page. &#xD;&#xA;&#xD;&#xA;Questions and suggestions are always welcomed!\\&#xD;&#xA;Happy coding!</s:content>
        <s:mTime>2007-01-09 02:34:21.845</s:mTime>
        <s:cTime>2007-01-09 00:46:21.871</s:cTime>
        <s:comments>
            <rdf:Bag>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-1'
                         s:name='comment-start/2007-01-09/1-1'
                         s:cUser='jff'
                         s:oUser='jff'
                         s:mUser='jff'>
                        <s:content>Great post! Well-written and very useful! However, I must advert you for the biggest disadvantage of CAPTCHA systems: inaccessibility.&#xD;&#xA;&#xD;&#xA;I suggest you to take a look at W3 document &quot;Inaccessibility of CAPTCHA&quot; ( http://www.w3.org/TR/turingtest/ ).&#xD;&#xA;&#xD;&#xA;Cheers,&#xD;&#xA;JFF&#xD;&#xA;&#xD;&#xA;PS: I love the new design :-)</s:content>
                        <s:mTime>2007-01-09 09:10:56.348</s:mTime>
                        <s:cTime>2007-01-09 09:10:56.236</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-2'
                         s:name='comment-start/2007-01-09/1-2'
                         s:cUser='pabrantes'
                         s:oUser='pabrantes'
                         s:mUser='pabrantes'>
                        <s:content>Well it&apos;s true the way I&apos;ve implemented jcatpcha comes with inaccessibility issues, although JCaptcha and other CATPCHA systems have the &quot;play sound&quot; feature, although I haven&apos;t looked much into it. Maybe on a next version!&#xD;&#xA;&#xD;&#xA;A good example of a captcha system breaking the inaccessibility barriers is, for example, the {link:google&apos;s sitemap register page|url=https://www.google.com/accounts/NewAccount?service=sitemaps&amp;followup=http://www.google.com%2Fwebmasters%2Ftools%2Fsiteoverview%3Fhl%3Den&amp;continue=http://www.google.com%2Fwebmasters%2Ftools%2Fsiteoverview%3Fhl%3Den&amp;hl=en} where you also can play sound of the letters.&#xD;&#xA;&#xD;&#xA;I&apos;m glad you like the design, I think I&apos;m addicted to it! Can&apos;t stop looking ~~laugh~~</s:content>
                        <s:mTime>2007-01-09 11:07:57.719</s:mTime>
                        <s:cTime>2007-01-09 11:07:57.631</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-3'
                         s:name='comment-start/2007-01-09/1-3'
                         s:cUser='AdaHsu'
                         s:oUser='AdaHsu'
                         s:mUser='AdaHsu'>
                        <s:content>May I get this additional jar file with  JCatpchaSingleton and  JCatpchaServlet ?&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;</s:content>
                        <s:mTime>2007-04-05 18:47:25.155</s:mTime>
                        <s:cTime>2007-04-05 18:47:25.023</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-4'
                         s:name='comment-start/2007-01-09/1-4'
                         s:cUser='pabrantes'
                         s:oUser='pabrantes'
                         s:mUser='pabrantes'>
                        <s:content>Hello Ada, &#xD;&#xA;&#xD;&#xA;Sorry for the late reply but I was enjoying a 2 day &quot;vacation&quot;.\\ &#xD;&#xA;I&apos;ve uploaded snipsnap jar, although keep in mind that other modifications in the source code are made. All the modifications I&apos;ve done are documented in the blog and since you&apos;ve already read my posts I know you&apos;re aware of them.&#xD;&#xA;&#xD;&#xA;{link:Download snipsnap jar with jcaptcha implementation|url=http://storage.pabrantes.net/snipsnap.jar}. &#xD;&#xA;&#xD;&#xA;Also, count with an available SVN tree by tomorrow! (if you&apos;re interested)</s:content>
                        <s:mTime>2007-04-06 17:51:34.878</s:mTime>
                        <s:cTime>2007-04-06 17:51:34.736</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-5'
                         s:name='comment-start/2007-01-09/1-5'
                         s:cUser='pabrantes'
                         s:oUser='pabrantes'
                         s:mUser='pabrantes'>
                        <s:content>Hello again,&#xD;&#xA;&#xD;&#xA;You now can get it also doing a checkout on the {link:SVN repository|url=http://svn.pabrantes.net/repository/}. </s:content>
                        <s:mTime>2007-04-07 00:23:53.242</s:mTime>
                        <s:cTime>2007-04-07 00:23:53.116</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
                <rdf:li>
                    <s:Comment rdf:about='http://pabrantes.net/blog/rdf#comment-start/2007-01-09/1-6'
                         s:name='comment-start/2007-01-09/1-6'
                         s:cUser='AdaHsu'
                         s:oUser='AdaHsu'
                         s:mUser='AdaHsu'>
                        <s:content>Yes, I also enjoy 4 days vacation last week... :)</s:content>
                        <s:mTime>2007-04-09 03:11:31.581</s:mTime>
                        <s:cTime>2007-04-09 03:11:31.477</s:cTime>
                        <s:commentedSnip rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-09/1'/>
                    </s:Comment>
                </rdf:li>
            </rdf:Bag>
        </s:comments>
        <s:snipLinks>
            <rdf:Bag>
                <rdf:li rdf:resource='#snipsnap-notfound'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#pabrantes/post-history'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#start/2007-02-28/1'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#start/2007-03-06/1'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#start/2007-02-18/1'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#start/2007-01-19/1'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#SnipSnap/themes/pabrantesTheme/css/snip.css'/>
                <rdf:li rdf:resource='#AdaHsu'/>
                <rdf:li rdf:resource='#snipsnap-index'/>
                <rdf:li rdf:resource='http://pabrantes.net/blog/rdf#start/2006-11-26/1'/>
            </rdf:Bag>
        </s:snipLinks>
        <s:attachments
             rdf:type='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
    </s:Snip>
</rdf:RDF>
