验证码SimpleCaptcha
想总结一下验证码(开源使用的验证码。当然,也可以自己去实现,不过有成熟的开源项目就用吧)的内容,然后看了几个开源项目,发现SimpleCaptcha使用比较简单,容易入手,就从它开始吧。
接触SimpleCaptcha
1.下载 SimpleCaptcha
2.将下载的JAR包添加到项目中
3.配置Servlet
可使用的Servlet:StickyCaptchaServlet、SimpleCaptchaServlet、AudioCaptchaServlet。
StickyCaptchaServlet和SimpleCaptchaServlet产生图片验证码,AudioCaptchaServlet产生语音验证码。
StickyCaptchaServlet产生的验证码将保存在session中,刷新将不会重新创建。
StickyCaptchaServlet
are “sticky” to the user’s session: page reloads will render the same CAPTCHA instead of generating a new one.
下面写一个例子,简单的配置servlet,页面展示产生的验证码。
1 <servlet> 2 <servlet-name>StickyCaptcha</servlet-name> 3 <servlet-class>nl.captcha.servlet.StickyCaptchaServlet</servlet-class> 4 </servlet> 5 <servlet> 6 <servlet-name>SimpleCaptcha</servlet-name> 7 <servlet-class>nl.captcha.servlet.SimpleCaptchaServlet</servlet-class> 8 </servlet> 9 <servlet> 10 <servlet-name>AudioCpatcha</servlet-name> 11 <servlet-class>nl.captcha.servlet.AudioCaptchaServlet</servlet-class> 12 </servlet> 13 14 <servlet-mapping> 15 <servlet-name>StickyCaptcha</servlet-name> 16 <url-pattern>/sticky.png</url-pattern> 17 </servlet-mapping> 18 <servlet-mapping> 19 <servlet-name>SimpleCaptcha</servlet-name> 20 <url-pattern>/simple</url-pattern> 21 </servlet-mapping> 22 <servlet-mapping> 23 <servlet-name>AudioCpatcha</servlet-name> 24 <url-pattern>/audio</url-pattern> 25 </servlet-mapping>
1 <table> 2 <tr valign="top"> 3 <td>Sticky:</td> 4 <td><img src="http://localhost:8080/captcha/sticky.png" /> 5 </td> 6 </tr> 7 <tr valign="top"> 8 <td>Simple:</td> 9 <td><img src="http://localhost:8080/captcha/simple" /> 10 </td> 11 </tr> 12 <tr valign="top"> 13 <td>Audio:</td> 14 <td><audio id="audio" src="http://localhost:8080/captcha/audio" 15 controls="controls"> 浏览器不支持<audio> </audio></td> 16 </tr> 17 </table>
为什么传了三张效果图?当然不是无聊,三张是依次刷新后的效果图。发现Sticky部分的验证码刷新后也改变了,和上面的说明不一致啊。然后试验在页面上只放一个Sticky的验证码,刷新的确没改变。这是为什么呢?观察图片发现每次刷新收Sticky的内容都是上一次Simple的内容,恍然大悟......他们都往session里存了内容,而且存的AttributeName都一样,所以被覆盖了。
打印出session的内容后发现Sticky、Simple在session存入的名称是simpleCaptcha(常量Captcha.NAME);Audio存入的名称是audioCaptcha(常量AudioCaptcha.NAME),所以可以通过attributeName从session中取出对象并转化成Captcha或AudioCaptcha,然后通过getAnswer方法获取验证码的答案,或通过isCorrect(String answer)直接验证传入验证码是否在正确。
使用中还发现StickyCaptchaServlet在初次载入时无法正常产生返回内容。在http://simplecaptcha.git.sourceforge.net/git/gitweb.cgi?p=simplecaptcha/simplecaptcha;a=commit;h=4c3a9eddfefabab6492c89f5c3887ca622ce4b0f上看到这个bug已经被修复,但是用过程中依旧出现。
Sticky也不常用,按照目前的验证码习惯,采用的验证码都是刷新页面重新生成的方式。
上面的例子中只是展示的基本的验证码的效果,没有做验证,下面实现验证用户输入内容时候正确。
1 <form action="http://localhost:8080/captcha/answer.jsp" method="post"> 2 <table> 3 <tr valign="top"> 4 <td>Simple:</td> 5 <td><img src="http://localhost:8080/captcha/simple" /> 6 </td> 7 <td><input type="text" name="simpleAnswer" /> 8 </td> 9 </tr> 10 11 <tr valign="top"> 12 <td>Audio:</td> 13 <td><audio id="audio" src="http://localhost:8080/captcha/audio" 14 controls="controls"> 浏览器不支持<audio> </audio> 15 </td> 16 <td><input name="audioAnswer" /> 17 </td> 18 </tr> 19 </table> 20 <input type="submit" value="提交" /> 21 </form>
接收并判断验证码是否正确的JSP(蛋疼的JSP,在也不想用JSP了
1 <% 2 Captcha captcha = (Captcha) session.getAttribute(Captcha.NAME); 3 AudioCaptcha audioCaptcha = (AudioCaptcha) session 4 .getAttribute(AudioCaptcha.NAME); 5 if (captcha.getAnswer() 6 .equals(request.getParameter("simpleAnswer"))) { 7 %> 8 <B> Simple:Correct!</B> 9 <br /> 10 <% 11 } else { 12 %> 13 <B> Simple:Wrong!</B> 14 <br /> 15 <% 16 } 17 if (captcha.isCorrect(request.getParameter("audioAnswer"))) { 18 %> 19 <B> Audio:Correct!</B> 20 <% 21 } else { 22 %> 23 <B> Audio:Wrong!</B> 24 <% 25 } 26 %>
Audio也比较蛋疼,目前也不属于常用的,毕竟语音验证应用起来要求客户端必须有语音输出,而且对用户也是有要求啊,听不大好的话......
---------------------------------------------------------------------------------------------------------------------------------
上面使用了simplecaptcha包自带的Servlet,实现了简单的验证码产生及验证,下面开始拓展 simplecaptcha实现自定义的验证码。
编写Servlet接收验证码的请求,使用Captcha.build()产生验证码,将图片内容写到返回流中,so easy......
1 @Override 2 protected void doPost(HttpServletRequest req, HttpServletResponse resp) 3 throws ServletException, IOException { 4 // 简单的是设定图片长宽,添加内容 5 Captcha captcha = new Captcha.Builder(120, 38).addText().build(); 6 resp.setContentType("image/jpeg"); 7 OutputStream os = resp.getOutputStream(); 8 ImageIO.write(captcha.getImage(), "JPEG", os); 9 req.getSession().setAttribute("checkCode", captcha.getAnswer()); 10 }
上面的代码将验证码的答案存放在session中,当用户输入验证码并提交后可将用户输入内容与session中的内容比较来验证是否正确。
在深入一点,限制验证码的内容,去除0、1、l、O等容易混淆的内容,限定字体,设置背景等等等等......
addBackground()使用默认的BackgroundProducer添加背景,addBackground(BackgroundProducer)可以通过实现BackgroundProducer接口提供自己的背景生成器。
addBorder()在图片周围绘制一个但像素宽的边框。
addNoise()、addNoise(NoiseProducer nProd)分别通过默认的和提供的NoiseProducer设置“噪音”。使用默认的将在图片中加入横线。
gimp()、gimp(GimpyRenderer gimpy)通过默认的或者提供的GimpyRenderer去对图片进行模糊。
addText()使用默认的TextProducer创建验证码内容。
addText(TextProducer txtProd)使用提供的TextProducer创建验证码内容。
addText(TextProducer txtProd,WordRenderer wRenderer)使用提供的TextProducer创建验证码内容,使用提供的WordRenderer渲染内容。
addText(WordRenderer wRenderer)使用默认的TextProducer创建验证码内容,使用提供的WordRenderer渲染内容。
build()创建Captcha对象。
给出两个像样点的效果吧,自定义TextProducer及WordRanderer。
1 public class EasyCharTextProducer implements TextProducer { 2 3 private static final char[] chars = { 'a', 'c', 'd', 'e', 'f', 'h', 'j', 4 'k', 'm', 'n', 'p', 'r', 's', 't', 'w', 'x', 'y', '3', '4', '5', 5 '7', '8' }; 6 7 private static final int lastChar = chars.length; 8 9 private int length; 10 11 public EasyCharTextProducer() { 12 this(5); 13 } 14 15 public EasyCharTextProducer(int length) { 16 super(); 17 this.length = length; 18 } 19 20 public String getText() { 21 Random random = new Random(); 22 StringBuilder sb = new StringBuilder(length); 23 for (int i = 0; i < length; i++) { 24 int r = random.nextInt(lastChar); 25 sb.append(chars[r]); 26 } 27 return sb.toString(); 28 } 29 30 }
1 public class FixedWordRenderer implements WordRenderer { 2 // The text will be rendered 25%/5% of the image height/width from the X and 3 // Y axes 4 private static final double YOFFSET = 0.20; 5 private static final double XOFFSET = 0.15; 6 private static final int charDistance = 5; 7 private static final Color DEFAULT_COLOR = Color.BLACK; 8 private static final List<Font> DEFAULT_FONTS = new ArrayList<Font>(); 9 10 private final Color _color; 11 private final List<Font> _fonts; 12 13 static { 14 DEFAULT_FONTS.add(new Font("Arial", Font.BOLD, 40)); 15 DEFAULT_FONTS.add(new Font("Courier", Font.BOLD, 40)); 16 } 17 18 /** 19 * Will render the characters in black and in either 40pt Arial or Courier. 20 */ 21 public FixedWordRenderer() { 22 this(DEFAULT_COLOR, DEFAULT_FONTS); 23 } 24 25 public FixedWordRenderer(Color color, List<Font> fonts) { 26 _color = color != null ? color : DEFAULT_COLOR; 27 _fonts = fonts != null ? fonts : DEFAULT_FONTS; 28 } 29 30 /** 31 * Render a word onto a BufferedImage. 32 * 33 * @param word 34 * The word to be rendered. 35 * @param bi 36 * The BufferedImage onto which the word will be painted on to 37 */ 38 public void render(String word, BufferedImage image) { 39 Random random = new Random(); 40 Graphics2D g = image.createGraphics(); 41 42 RenderingHints hints = new RenderingHints( 43 RenderingHints.KEY_ANTIALIASING, 44 RenderingHints.VALUE_ANTIALIAS_ON); 45 hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, 46 RenderingHints.VALUE_RENDER_QUALITY)); 47 g.setRenderingHints(hints); 48 49 g.setColor(_color); 50 FontRenderContext frc = g.getFontRenderContext(); 51 int startPosX = (int) Math.round(image.getWidth() * XOFFSET); 52 int startPosY = image.getHeight() 53 - (int) Math.round(image.getHeight() * YOFFSET); 54 char[] wc = word.toCharArray(); 55 for (char element : wc) { 56 char[] itchar = new char[] { element }; 57 int choiceFont = random.nextInt(_fonts.size()); 58 Font itFont = _fonts.get(choiceFont); 59 g.setFont(itFont); 60 61 GlyphVector gv = itFont.createGlyphVector(frc, itchar); 62 double charWitdth = gv.getVisualBounds().getWidth(); 63 64 g.drawChars(itchar, 0, itchar.length, startPosX, startPosY); 65 startPosX = startPosX + (int) charWitdth 66 + random.nextInt(charDistance) - 2; 67 } 68 } 69 70 }
创建Captcha的代码及对应效果
1 Captcha captcha = new Captcha.Builder(120, 38) 2 .addText(new EasyCharTextProducer(4), 3 new FixedWordRenderer(Color.white, englishFonts)) 4 .addNoise() 5 .addNoise(new CurvedLineNoiseProducer(Color.gray, 1.5f)) 6 .addBorder().addBackground(new GradiatedBackgroundProducer()).build();
1 Captcha captcha = new Captcha.Builder(120, 38) 2 .addText(new EasyCharTextProducer(4), 3 new FixedWordRenderer(Color.white, englishFonts)) 4 .gimp(new FishEyeGimpyRenderer()).addBorder().build();
要更好的效果就需要去了解gimpy、noise、background等包下面提供的randerer、producer的效果以及自己去实现接口提供自己的randerer和producer,当然,审美很重要......