想總結一下驗證碼(開源使用的驗證碼。當然,也可以自己去實現,不過有成熟的開源項目就用吧)的內容,然后看了幾個開源項目,發現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,頁面展示產生的驗證碼。
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也不常用,按照目前的驗證碼習慣,采用的驗證碼都是刷新頁面重新生成的方式。
上面的例子中只是展示的基本的驗證碼的效果,沒有做驗證,下面實現驗證用戶輸入內容時候正確。
Form表單
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了
驗證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。
EasyCharTextProducer
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 }
FixedWordRenderer
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,當然,審美很重要......

Servlet配置