基於位置驗證的圖形驗證碼
作者:邵發
官網:http://afanihao.cn/java
本文是Java學習指南系列教程的官方配套文檔。內容介紹一種基於位置的驗證碼的實現,附演示說明和項目源碼。
1. 基於位置的圖形驗證碼
下面演示一種基於位置的驗證碼,示意圖如下。
( 項目演示http://127.0.0.1:8080/demo/test )
和所有的驗證碼一樣,此驗證碼依賴人的視覺。用戶需要用鼠標文字在圖片中的出現位置。當用戶點中正確時,則驗證通過。否則驗證失敗。
2. 驗證碼圖片的生成
總體上,後臺需要隨機的生成一張圖片,將等識別目標和干擾目標都繪製在圖片中。此圖片在前端頁面中顯示,前端將用戶點擊的位置座標(clickX, clickY)發給後臺,後臺校驗點擊的位置是否正確。
首先,後臺隨機挑選一些文字用於顯示,其中一個文字爲識別目標,其他文字爲干擾文字。例如,drawChars = "羊馬猴",選取“猴”爲識別目標。
public void random()
{
Random rand = new Random();
drawChars = "";
while(true)
{
int idx = rand.nextInt(charBase.length());
char ch = this.charBase.charAt(idx);
if( drawChars.indexOf(ch) <0)
{
drawChars += ch;
if(drawChars.length()>=3) break;
}
}
//
targetIndex = rand.nextInt(drawChars.length());
targetChar = drawChars.charAt(targetIndex);
}
然後,將這些文字繪製爲一個PNG圖片。使用Java Swing中的繪製技術,將文字以傾斜方式繪製。
FontMetrics fm = g2d.getFontMetrics(g2d.getFont());
int fontSize = fm.getHeight(); // 字高
int textWidth = fm.stringWidth(theChar + "");
int descent = fm.getDescent(); // bottom->baseline 的高度
AffineTransform saveAT = g2d.getTransform();
g2d.translate((double)cx, (double)cy);
g2d.rotate(angle);
g2d.setPaint(fontColor);
g2d.drawString(theChar + "", -textWidth/2, fontSize/2-descent);
g2d.setTransform(saveAT);
其中,cx, cy是文字隨機顯示的位置,angle是文字的旋轉角度, fontColor是一個隨機的顏色。最終繪製出來的圖片,各個要素都是隨機的。
3. 驗證流程
3.1 /verify/refresh.do
前端首先應該調用這個接口,來刷新驗證碼。
@RequestMapping("/verify/refresh.do")
public Object refresh( HttpSession session)
{
// 產生新的隨機驗證碼,放在Session裏
VerifyPng verify = new VerifyPng();
verify.random();
session.setAttribute("verify", verify);
Map<String,Object> data = new HashMap<>();
data.put("targetChar", "" + verify.targetChar );
return new AfRestData(data);
}
生成的驗證信息放在Session中,verify對象中包含了識別目標出現的位置。
3.2 /verify/show
前端調用此接口顯示圖片。注意,這個是一個僞靜態URL,直接作一張圖片URL顯示。
@GetMapping("/verify/show")
public void show ( HttpSession session
, HttpServletResponse response) throws Exception
{
// 生成PNG發給客戶端
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
VerifyPng verify = (VerifyPng)session.getAttribute("verify");
verify.toPNG(response.getOutputStream());
}
前端需要把這張圖片呈現到用戶頁面裏顯示。
3.3 /verify/check.do
用戶根據提示,肉眼觀察圖片,鼠標點擊識別目標文字。前端JS響應鼠標點擊動作,將鼠標點擊的位置發給後臺,由後臺校驗。
@PostMapping("/verify/check.do")
public Object check ( HttpSession session
, @RequestBody JSONObject jreq) throws Exception
{
VerifyPng v = (VerifyPng)session.getAttribute("verify");
session.removeAttribute("verify");
if(v== null)
return new AfRestError(-8, "驗證碼校驗出錯");
int clickX = jreq.getIntValue("clickX");
int clickY = jreq.getIntValue("clickY");
。。。
}
4. 前端顯示
開始時,前端需要調用 /verify/refresh.do 來刷新驗碼圖片。
vf.refresh = function(){
var req = {};
Af.rest('[[@{/verify/refresh.do}]]' , req, function(data){
vf.status = 0;
vf.repaint();
$('.targetChar').text( data.targetChar);
})
}
刷新完成後,將新的驗證碼顯示到界面上,
vf.repaint = function(){
var png = new Image();
png.src = '[[@{/verify/show}]]?t=' + new Date().getTime() ;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var w = canvas.width;
var h = canvas.height;
png.onload = function(){
ctx.drawImage( png, 0, 0);
}
}
當用戶點擊Canvas時,調用 /verify/check.do 進行驗證,
vf.check = function(clickX, clickY){
var req ={};
req.clickX = clickX;
req.clickY = clickY;
Af.rest('[[@{/verify/check.do}]]' , req, function(data){
vf.status = 100;
vf.showResult(true, clickX, clickY);
},
function(error,reason){
vf.status = -1;
vf.showResult(false, clickX, clickY);
})
}
當驗證成功後時,在 Canvas上繪製一個OK圖標。如果失敗,繪製一個出錯的圖標。
示例中,驗證圖片是240x80像素,每次顯示3個文字,可以根據實際項目中的需要自行調試。以上演示所用的項目源碼和JAR包在此處可以獲取