在應(yīng)用程序中為防止系統(tǒng)被攻擊程序自動(dòng)訪問(wèn),通常提供一個(gè)人眼容易識(shí)別,但程序很難識(shí)別的圖形,圖形內(nèi)是隨機(jī)產(chǎn)生的一些字符。為防止被攻擊程序自動(dòng)識(shí)別,字符通常會(huì)在位置和顏色上作隨機(jī)處理。
為便于使用,本人用 java實(shí)現(xiàn)了一個(gè)生成隨機(jī)字符圖片的通用類,封裝了生成過(guò)程的復(fù)雜性,能非常方便的使用。
實(shí)現(xiàn)類類名為RandomGraphic,它由一個(gè)靜態(tài)工廠方法createInstance(int charCount)來(lái)創(chuàng)建對(duì)象實(shí)例,charCount指定圖片中字符的個(gè)數(shù),最多16個(gè)。
提供了兩個(gè)方法來(lái)生成隨機(jī)圖片,一個(gè)方法String drawNumber(String graphicFormat,OutputStream out) 生成的圖片中都是隨機(jī)數(shù)字,由0-9組成。
另一個(gè)方法String drawAlpha(String graphicFormat,OutputStream out)生成的圖片中都是隨機(jī)字母,由a-z組成,生成的字符個(gè)數(shù)在工廠方法中指定。將數(shù)字和圖片分開(kāi)是因?yàn)橛行?shù)字和字母人眼看上去很難區(qū)分,反而影響用戶輸入。
graphicFormat為生成圖片格式,取常量值GRAPHIC_JPEG 或 GRAPHIC_PNG
out用來(lái)輸出生成的圖片文件內(nèi)容,可以是一個(gè)文件,或servlet中輸出到客戶端的流,以便于在頁(yè)面顯示。
下面給出原碼和在servlet中的示例代碼。
package net;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
/**
* 生成隨機(jī)數(shù)字或字母串,以圖像方式顯示,用于人工識(shí)別,使程序很難識(shí)別。
* 減小系統(tǒng)被程序自動(dòng)攻擊的可能性。
* 生成的圖形顏色由紅、黑、藍(lán)、紫4中隨機(jī)組合而成,數(shù)字或字母垂直方向位置在
* 一定范圍內(nèi)也是隨機(jī)的,減少被程序自動(dòng)識(shí)別的幾率。
* 由于數(shù)字的0,1,2易和字母的o,l,z混淆,使人眼難以識(shí)別,因此不生成數(shù)字
* 和字母的混合串。
* 生成的串字母統(tǒng)一用小寫,串的最大長(zhǎng)度為16。
*
* @version
* @Since
* @See Also
* @author lchen
* Create Date 2005-12-16
*
*/
public class RandomGraphic {
//字符的高度和寬度,單位為像素
private int wordHeight = 10;
private int wordWidth = 15;
//字符大小
private int fontSize = 16;
//最大字符串個(gè)數(shù)
private static final int MAX_CHARCOUNT = 16;
//垂直方向起始位置
private final int initypos = 5;
//要生成的字符個(gè)數(shù),由工廠方法得到
private int charCount = 0;
//顏色數(shù)組,繪制字串時(shí)隨機(jī)選擇一個(gè)
private static final Color[] CHAR_COLOR = {Color.RED,Color.BLUE,Color.GREEN,Color.MAGENTA};
//隨機(jī)數(shù)生成器
private Random r = new Random();
/**
* 生成圖像的格式常量,JPEG格式,生成為文件時(shí)擴(kuò)展名為.jpg;
* 輸出到頁(yè)面時(shí)需要設(shè)置MIME type 為image/jpeg
*/
public static String GRAPHIC_JPEG = "JPEG";
/**
* 生成圖像的格式常量,PNG格式,生成為文件時(shí)擴(kuò)展名為.png;
* 輸出到頁(yè)面時(shí)需要設(shè)置MIME type 為image/png
*/
public static String GRAPHIC_PNG = "PNG";
//用工廠方法創(chuàng)建對(duì)象
protected RandomGraphic(int charCount){
this.charCount = charCount;
}
/**
* 創(chuàng)建對(duì)象的工廠方法
* @param charCount 要生成的字符個(gè)數(shù),個(gè)數(shù)在1到16之間
*
* Return 返回RandomGraphic對(duì)象實(shí)例
* @throws Exception 參數(shù)charCount錯(cuò)誤時(shí)拋出
*/
public static RandomGraphic createInstance(int charCount) throws Exception{
if (charCount < 1 || charCount > MAX_CHARCOUNT){
throw new Exception("Invalid parameter charCount,charCount should between in 1 and 16");
}
return new RandomGraphic(charCount);
}
/**
* 隨機(jī)生成一個(gè)數(shù)字串,并以圖像方式繪制,繪制結(jié)果輸出到流out中
*
* @param graphicFormat 設(shè)置生成的圖像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 圖像結(jié)果輸出流
* @return 隨機(jī)生成的串的值
* @throws IOException
*/
public String drawNumber(String graphicFormat,OutputStream out) throws IOException{
// 隨機(jī)生成的串的值
String charValue = "";
charValue = randNumber();
return draw(charValue,graphicFormat,out);
}
/**
* 隨機(jī)生成一個(gè)字母串,并以圖像方式繪制,繪制結(jié)果輸出到流out中
*
* @param graphicFormat 設(shè)置生成的圖像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 圖像結(jié)果輸出流
* @return 隨機(jī)生成的串的值
* @throws IOException
*/
public String drawAlpha(String graphicFormat,OutputStream out) throws IOException{
// 隨機(jī)生成的串的值
String charValue = "";
charValue = randAlpha();
return draw(charValue,graphicFormat,out);
}
/**
* 以圖像方式繪制字符串,繪制結(jié)果輸出到流out中
* @param charValue 要繪制的字符串
* @param graphicFormat 設(shè)置生成的圖像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 圖像結(jié)果輸出流
* @return 隨機(jī)生成的串的值
* @throws IOException
*/
protected String draw(String charValue,String graphicFormat,OutputStream out) throws IOException{
//計(jì)算圖像的寬度和高度
int w = (charCount+2) * wordWidth;
int h = wordHeight * 3;
//創(chuàng)建內(nèi)存圖像區(qū)
BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = bi.createGraphics();
//設(shè)置背景色
Color backColor = Color.WHITE;
g.setBackground(backColor);
g.fillRect(0,0,w,h);
//設(shè)置font
g.setFont(new Font(null,Font.BOLD,fontSize));
//繪制charValue,每個(gè)字符顏色隨機(jī)
for(int i = 0; i < charCount; i++){
String c = charValue.substring(i,i+1);
Color color = CHAR_COLOR[randomInt(0,CHAR_COLOR.length)];
g.setColor(color);
int xpos = (i+1) * wordWidth;
//垂直方向上隨機(jī)
int ypos = randomInt(initypos+wordHeight,initypos+wordHeight*2);
g.drawString(c,xpos,ypos);
}
g.dispose();
bi.flush();
// 輸出到流
ImageIO.write(bi,graphicFormat,out);
return charValue;
}
protected String randNumber(){
String charValue = "";
//生成隨機(jī)數(shù)字串
for (int i = 0; i < charCount; i++){
charValue += String.valueOf(randomInt(0,10));
}
return charValue;
}
private String randAlpha(){
String charValue = "";
//生成隨機(jī)字母串
for (int i = 0; i < charCount; i++){
char c = (char) (randomInt(0,26)+‘a(chǎn)‘);
charValue += String.valueOf(c);
}
return charValue;
}
/**
* 返回[from,to)之間的一個(gè)隨機(jī)整數(shù)
*
* @param from 起始值
* @param to 結(jié)束值
* @return [from,to)之間的一個(gè)隨機(jī)整數(shù)
*/
protected int randomInt(int from,int to){
//Random r = new Random();
return from+r.nextInt(to-from);
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
System.out.println(RandomGraphic.createInstance(5).drawAlpha(RandomGraphic.GRAPHIC_PNG,new FileOutputStream("c:/myimg.png")));
}
}
RandomGraphic類原代碼結(jié)束
在servlet中使用該類,將圖片輸出到客戶端,在頁(yè)面上就可顯示隨機(jī)圖片
package net;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RandImage extends HttpServlet {
public RandImage() {
super();
}
protected void doGet(HttpServletRequest req,HttpServletResponse res) throws IOException,ServletException{
//設(shè)置輸出內(nèi)容為圖像,格式為jpeg
res.setContentType("image/jpg");
try {
//將內(nèi)容輸出到響應(yīng)客戶端對(duì)象的輸出流中,生成的圖片中包含6個(gè)字符
String v = RandomGraphic.createInstance(6).drawAlpha(RandomGraphic.GRAPHIC_JPEG,res.getOutputStream());
//將字符串的值保留在session中,便于和用戶手工輸入的驗(yàn)證碼比較,比較部分不是本文討論重點(diǎn),故略
req.getSession().setAttribute("rv",v);
} catch (Exception e) {
e.printStackTrace();
}
}
}
需要在web.xml中配置該servlet
<servlet>
<servlet-name>RandImage</servlet-name>
<servlet-class>net.RandImage</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RandImage</servlet-name>
<url-pattern>/RandImage</url-pattern>
</servlet-mapping>
然后在一個(gè)頁(yè)面中用下面的代碼來(lái)顯示圖片
<html>
<body>
驗(yàn)證碼: <image src="RandImage" />
</body>
</html>
要增加圖片的識(shí)別難度,還可以在draw方法中對(duì)圖象進(jìn)行一定程度變形回旋轉(zhuǎn)處理,或者在圖片中添加隨機(jī)干擾線條,但要保證用人眼能比較容易識(shí)別。