新的Seam CAPTCHA很棒

发布者    |       Seam

Seam提供了一些基本的CAPTCHA创建和验证基础设施,因此如果您想将CAPTCHA验证添加到表单,您只需添加一个表单字段并显示图片<h:graphicImage/>。Seam 1.x和2.0中提供的唯一内置实现是基于JCaptcha,但您可以轻松扩展它并执行自己的问题/答案操作。这正是我所做的,如果您尝试发表评论,您可以看到我的简化数学问题CAPTCHA。

我不是唯一遇到JCaptcha问题的Seam用户(如果您想了解更多,请在Seam论坛上搜索,基本上:它过度设计,启动需要几秒钟,有时渲染图片需要几秒钟,默认的图像生成器难以阅读,但CAPTCHA仍然容易被破解等)。因此,Gavin编写了一个新的Captcha实现,我们可以将其与Seam 2.0.1一起发布。这现在在Seam CVS中,尚未发布,所以如果您不是Seam CVS用户,请忽略这篇博客文章,并在我们发布2.0.1时再回来。

使用场景仍然是相同的,在您的表单中添加一个输入字段和图片

<s:validateAll>
    <h:inputText size="6" maxlength="6" required="true" id="verifyCaptcha" value="#{captcha.response}"/>
    <h:graphicImage value="/seam/resource/captcha"/>
</s:validateAll>

默认情况下,Gavin的实现仅以无任何混淆的方式将简单的数学问题渲染为图片。因此,我扩展了内置类。这是我的生成的CAPTCHA问题看起来像这样

技巧是告诉用户忽略任何《圆圈》 - 并且永远不要使用任何看起来像圆圈的字符(零、o、O)。我不认为人们会难以解读这些字符,我在测试期间尝试了数十次,只失败了两次或三次。我认为这个CAPTCHA很难自动破解,用于混淆的真实文本和灰色阴影相同,圆圈实际上破坏了字符的原始形状。如果它被破解,我们只需以小的增量步骤添加更多侵略性的圆圈或增加字符的旋转范围。

让CAPTCHAs不那么痛苦的另一件事是将数据存储在HTTP会话中,这样如果用户在您的网站上输入过一次CAPTCHA,您就不需要再次输入。但所有这些都内置在新的Seam Captcha功能中。

以下是图像生成的自定义代码

package org.jboss.seam.wiki.core.captcha;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.captcha.Captcha;
import org.jboss.seam.captcha.CaptchaResponse;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

@Name("org.jboss.seam.captcha.captcha")
@Scope(ScopeType.SESSION)
@Install(precedence = Install.APPLICATION)
public class WikiCaptcha extends Captcha {

    Color backgroundColor = new Color(0xf5,0xf5, 0xf5);
    Font textFont = new Font("Arial", Font.PLAIN, 25);
    int charsToPrint = 6;
    int width = 120;
    int height = 25;
    int circlesToDraw = 4;
    float horizMargin = 20.0f;
    double rotationRange = 0.2;
    String elegibleChars = "ABDEFGHJKLMRSTUVWXYabdefhjkmnrstuvwxy23456789";
    char[] chars = elegibleChars.toCharArray();

    @Override
    @Create
    public void init() {
        super.init();

        StringBuffer finalString = new StringBuffer();
        for (int i = 0; i < charsToPrint; i++) {
            double randomValue = Math.random();
            int randomIndex = (int) Math.round(randomValue * (chars.length - 1));
            char characterToShow = chars[randomIndex];
            finalString.append(characterToShow);
        }

        setChallenge(finalString.toString());
        setCorrectResponse(finalString.toString());
    }

    @Override
    public BufferedImage renderChallenge() {

        // Background
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
        g.setColor(backgroundColor);
        g.fillRect(0, 0, width, height);

        // Some obfuscation circles
        for (int i = 0; i < circlesToDraw; i++) {
            int circleColor = 80 + (int)(Math.random() * 70);
            float circleLinewidth = 0.3f + (float)(Math.random());
            g.setColor(new Color(circleColor, circleColor, circleColor));
            g.setStroke(new BasicStroke(circleLinewidth));
            int circleRadius = (int) (Math.random() * height / 2.0);
            int circleX = (int) (Math.random() * width - circleRadius);
            int circleY = (int) (Math.random() * height - circleRadius);
            g.drawOval(circleX, circleY, circleRadius * 2, circleRadius * 2);
        }

        // Text
        g.setFont(textFont);
        FontMetrics fontMetrics = g.getFontMetrics();
        int maxAdvance = fontMetrics.getMaxAdvance();
        int fontHeight = fontMetrics.getHeight();
        float spaceForLetters = -horizMargin * 2 + width;
        float spacePerChar = spaceForLetters / (charsToPrint - 1.0f);

        char[] allChars = getChallenge().toCharArray();
        for (int i = 0; i < allChars.length; i++ ) {
            char charToPrint = allChars[i];
            int charWidth = fontMetrics.charWidth(charToPrint);
            int charDim = Math.max(maxAdvance, fontHeight);
            int halfCharDim = (charDim / 2);
            BufferedImage charImage = new BufferedImage(charDim, charDim, BufferedImage.TYPE_INT_ARGB);
            Graphics2D charGraphics = charImage.createGraphics();
            charGraphics.translate(halfCharDim, halfCharDim);
            double angle = (Math.random() - 0.5) * rotationRange;
            charGraphics.transform(AffineTransform.getRotateInstance(angle));
            charGraphics.translate(-halfCharDim, -halfCharDim);
            int charColor = 60 + (int)(Math.random() * 90);
            charGraphics.setColor(new Color(charColor, charColor, charColor));
            charGraphics.setFont(textFont);
            int charX = (int) (0.5 * charDim - 0.5 * charWidth);
            charGraphics.drawString("" + charToPrint, charX, ((charDim - fontMetrics.getAscent())/2 + fontMetrics.getAscent()));
            float x = horizMargin + spacePerChar * (i) - charDim / 2.0f;
            int y = ((height - charDim) / 2);
            g.drawImage(charImage, (int) x, y, charDim, charDim, null, null);

            charGraphics.dispose();
        }
        g.dispose();

        return bufferedImage;
    }

    @CaptchaResponse(message = "#{messages['lacewiki.label.VerificationError']}")
    public String getResponse() {
        return super.getResponse();
    }
}

(如果您觉得某些代码看起来很熟悉,您可能之前在这里见过:这里.)

不过,我还需要完成一些其他事情,才能升级运行本站的新Captcha软件。因此,如果您想尝试它,请获取Seam CVS并将此类放入您的代码库中,无需其他配置。


回到顶部