在應用程式中為防止系統被攻擊程式自動訪問,通常提供一個人眼容易識別,但程式很難識別的圖形,圖形內是隨機產生的一些字元。為防止被攻擊程式自動識別,字元通常會在位置和顏色上作隨機處理。
為便於使用,本人用 java實現了一個產生隨機字元圖片的通用類,封裝了產生過程的複雜性,能非常方便的使用。
實作類別類名為RandomGraphic,它由一個靜態Factory 方法createInstance(int charCount)來建立對象執行個體,charCount指定圖片中字元的個數,最多16個。
提供了兩個方法來產生隨機圖片,一個方法String drawNumber(String graphicFormat,OutputStream out) 產生的圖片中都是隨機數字,由0-9組成。
另一個方法String drawAlpha(String graphicFormat,OutputStream out)產生的圖片中都是隨機字母,由a-z組成,產生的字元個數在Factory 方法中指定。將數字和圖片分開是因為有些數字和字母人眼看上去很難區分,反而影響使用者輸入。
graphicFormat為產生圖片格式,取常量值GRAPHIC_JPEG 或 GRAPHIC_PNG
out用來輸出產生的圖片檔案內容,可以是一個檔案,或servlet中輸出到用戶端的流,以便於在頁面顯示。
下面給出原碼和在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;
/**
* 產生隨機數字或字母串,以映像方式顯示,用於人工識別,使程式很難識別。
* 減小系統被程式自動攻擊的可能性。
* 產生的圖形顏色由紅、黑、藍、紫4中隨機組合而成,數字或字母垂直方向位置在
* 一定範圍內也是隨機的,減少被程式自動識別的幾率。
* 由於數位0,1,2易和字母的o,l,z混淆,使人眼難以識別,因此不產生數字
* 和字母的混合串。
* 產生的串字母統一用小寫,串的最大長度為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;
//最大字串個數
private static final int MAX_CHARCOUNT = 16;
//垂直方向起始位置
private final int initypos = 5;
//要產生的字元個數,由Factory 方法得到
private int charCount = 0;
//顏色數組,繪製字串時隨機播放一個
private static final Color[] CHAR_COLOR = {Color.RED,Color.BLUE,Color.GREEN,Color.MAGENTA};
//隨機數產生器
private Random r = new Random();
/**
* 產生映像的格式常量,JPEG格式,產生為檔案時副檔名為.jpg;
* 輸出到頁面時需要設定MIME type 為image/jpeg
*/
public static String GRAPHIC_JPEG = "JPEG";
/**
* 產生映像的格式常量,PNG格式,產生為檔案時副檔名為.png;
* 輸出到頁面時需要設定MIME type 為image/png
*/
public static String GRAPHIC_PNG = "PNG";
//用Factory 方法建立對象
protected RandomGraphic(int charCount){
this.charCount = charCount;
}
/**
* 建立對象的Factory 方法
* @param charCount 要產生的字元個數,個數在1到16之間
*
* Return 返回RandomGraphic對象執行個體
* @throws Exception 參數charCount錯誤時拋出
*/
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);
}
/**
* 隨機產生一個數字串,並以映像方式繪製,繪製結果輸出到流out中
*
* @param graphicFormat 設定產生的映像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 映像結果輸出資料流
* @return 隨機產生的串的值
* @throws IOException
*/
public String drawNumber(String graphicFormat,OutputStream out) throws IOException{
// 隨機產生的串的值
String charValue = "";
charValue = randNumber();
return draw(charValue,graphicFormat,out);
}
/**
* 隨機產生一個字母串,並以映像方式繪製,繪製結果輸出到流out中
*
* @param graphicFormat 設定產生的映像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 映像結果輸出資料流
* @return 隨機產生的串的值
* @throws IOException
*/
public String drawAlpha(String graphicFormat,OutputStream out) throws IOException{
// 隨機產生的串的值
String charValue = "";
charValue = randAlpha();
return draw(charValue,graphicFormat,out);
}
/**
* 以映像方式繪製字串,繪製結果輸出到流out中
* @param charValue 要繪製的字串
* @param graphicFormat 設定產生的映像格式,值為GRAPHIC_JPEG或GRAPHIC_PNG
* @param out 映像結果輸出資料流
* @return 隨機產生的串的值
* @throws IOException
*/
protected String draw(String charValue,String graphicFormat,OutputStream out) throws IOException{
//計算映像的寬度和高度
int w = (charCount+2) * wordWidth;
int h = wordHeight * 3;
//建立記憶體配置圖像區
BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = bi.createGraphics();
//設定背景色
Color backColor = Color.WHITE;
g.setBackground(backColor);
g.fillRect(0,0,w,h);
//設定font
g.setFont(new Font(null,Font.BOLD,fontSize));
//繪製charValue,每個字元顏色隨機
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;
//垂直方向上隨機
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 = "";
//產生隨機數字串
for (int i = 0; i < charCount; i++){
charValue += String.valueOf(randomInt(0,10));
}
return charValue;
}
private String randAlpha(){
String charValue = "";
//產生隨機字母串
for (int i = 0; i < charCount; i++){
char c = (char) (randomInt(0,26)+'a');
charValue += String.valueOf(c);
}
return charValue;
}
/**
* 返回[from,to)之間的一個隨機整數
*
* @param from 起始值
* @param to 結束值
* @return [from,to)之間的一個隨機整數
*/
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類原代碼結束
在servlet中使用該類,將圖片輸出到用戶端,在頁面上就可顯示隨機圖片
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{
//設定輸出內容為映像,格式為jpeg
res.setContentType("image/jpg");
try {
//將內容輸出到響應用戶端對象的輸出資料流中,產生的圖片中包含6個字元
String v = RandomGraphic.createInstance(6).drawAlpha(RandomGraphic.GRAPHIC_JPEG,res.getOutputStream());
//將字串的值保留在session中,便於和使用者手工輸入的驗證碼比較,比較部分不是本文討論重點,故略
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>
然後在一個頁面中用下面的代碼來顯示圖片
<html>
<body>
驗證碼: <image src="RandImage" />
</body>
</html>
要增加圖片的識別難度,還可以在draw方法中對圖象進行一定程度變形迴旋轉處理,或者在圖片中添加隨機幹擾線條,但要保證用人眼能比較容易識別。