191122_02 前後臺聯合驗證的驗證碼

前後臺聯合驗證的驗證碼

作者:邵發

官網:http://afanihao.cn/java

本文是Java學習指南系列教程的官方配套文檔。內容介紹另一種安全的驗證碼技術,即由後臺負責生成和驗證,使整個驗證流程不可輕易攻擊。本文附帶項目源碼及相關JAR包。

1.  驗證碼的作用

上一篇文章已經說過,驗證碼是用於“防刷”的,防止用戶或機器人的高頻率的網頁刷新。舉一個例子,假設網站提供一個訂單查詢功能,示意圖如下。

( 項目演示http://127.0.0.1:8080/demo/test  )

所謂的惡意刷新,就是瘋狂地點擊這個“查詢”按鈕。由於後臺的查詢操作往往要查詢數據庫,這個操作佔用CPU較高。如果對用戶的惡意刷新不加阻攔,則網站會由於負載太高而崩潰。

所以對高耗資源的服務接口,一般要使用驗證碼加以保護,使其無法輕易調用。加上驗證碼環節之後,用戶必須在人工輸入了正確的驗證碼之後,才能夠進入後面的查詢流程。輸入驗證碼需要一定時間,從而阻止了用戶的高頻刷新攻擊。

 

2.  後臺驗證碼的實現

驗證碼在工作原理上分爲兩種:純前端驗證,前端+後臺聯合驗證。本文介紹的是後臺驗證方式,這種方式依靠後臺來生成和校驗,過程安全可靠,不會輕易地被攻擊。

2.1 生成驗證碼

後臺添加一個接口,用於生成新的驗證碼,並放在Session裏。

@RequestMapping("/verify/refresh.do")
public Object refresh( HttpSession session)
{
	// 產生新的隨機驗證碼,放在Session裏
	VerifyPng v = new VerifyPng();
	String verifyCode = v.randomChar(4);
	session.setAttribute("verifyCode", verifyCode);
	return new AfRestData("");
}

 

2.2 顯示驗證碼圖片

由後臺負責動態的生成一個驗證碼的圖片,交給前端顯示。

@GetMapping("/verify/show")
public void show ( HttpSession session
			, HttpServletResponse response) throws Exception
{
	// 取得當前校驗碼
	String verifyCode = (String)session.getAttribute("verifyCode");

	// 生成PNG發給客戶端
	response.setContentType("image/png");
	response.setHeader("Cache-Control", "no-cache");
	VerifyPng v = new VerifyPng();
	v.toPNG(verifyCode, response.getOutputStream());
}

其中,由於驗證碼圖片是動態變化的,所以應答頭部要設置Cache-Control: no-cache。這樣,前端只需要顯示 http://your.com/verify/show 這張圖片就可以了。

前端實現:

(1) 放一個 <img> 控件用於顯示驗證圖片

<img class='verifyImg' style='width:80px;height:30px;background-color: #f5f59e;'>

(2) 然後動態的刷新驗證碼:

var req = {};
Af.rest('[[@{/verify/refresh.do}]]' , req, function(data){
	var verifyImgUrl = '[[@{/verify/show}]]?t=' + new Date().getTime();
	$('.verifyImg').attr('src', verifyImgUrl);
})

也就是說,先調用 /verify/refresh.do 接口來刷新生成新的驗證碼,然後讓<img>控件顯示新的驗證碼圖片即可。

 

2.3 提交驗證

用戶輸入驗證碼,點查詢按鈕。

(1) 前端把用戶的輸入提交到後臺,交由後臺驗證。

var req = {};
req.verifyCode = $('.verifyCode').val().trim();
	req.orderNumber = $('.orderNumber').val().trim();
	Af.rest ('[[@{/query.do}]]' ,req, function(data){
	alert('成功提交');
},

function(error, reason){
	// 如果是驗證碼錯誤,則重置驗證碼
	if(error == -8){
		vf.reset();
	}
	alert(reason);
})

(2) 後臺負責校驗用戶的輸入是否正確

後臺從請求裏取得用戶的輸入,然後與當前Session的驗證碼比較。如果驗證碼不匹配,則返回相應的提示給客戶端。

@PostMapping( "/query.do")
public Object query(HttpSession session
				, @RequestBody JSONObject jreq)
{
	// 取得用戶傳上來的輸入值
	String verifyCode = jreq.getString("verifyCode");
	if(! VerifyController.verify(session, verifyCode))
		return new AfRestError(-8, "驗證碼錯誤!");

	return new AfRestData("");
}

 

3.  後臺驗證碼圖片的動態生成

在後臺可以用Java直接生成一張驗證碼圖片,技術上也不復雜,就是創建一個BufferedImage然後在其上繪製即可。在Java學習指南系列的《Swing高級篇》裏有充分的演示講解。

生成圖片的關鍵代碼如下。

Graphics2D g2d = image.createGraphics();
g2d.setPaint(new Color(0xB03060)); // 文本顏色
g2d.setFont(new Font("微軟雅黑", Font.PLAIN, height-6));

FontMetrics fm = g2d.getFontMetrics(g2d.getFont());
int fontSize = fm.getHeight(); // 字高
int textWidth = fm.stringWidth(text);
int leading = fm.getLeading();
int ascent = fm.getAscent(); // top -> baseline 的高度
int descent = fm.getDescent(); // bottom->baseline 的高度

Rectangle rect = new Rectangle(width,height);
int x = rect.x + (rect.width - textWidth)/2; // 水平居中
int y = rect.y + rect.height /2 + (fontSize-leading)/2 - descent; // 豎直居中
g2d.drawString(text, x, y);

還可以在上面疊加繪製一些半透明的干擾點,示例代碼如下:

Random rand = new Random();
for(int i=0;i<4;i++)
{
	int r = rand.nextInt(255);
	int g = rand.nextInt(255);
	int b = rand.nextInt(255);
	int posX = rand.nextInt(width-10);
	int posY = rand.nextInt(height-10);
	g2d.setPaint(new Color(r,g,b, 60));
	g2d.fillOval(posX, posY, 10, 10);
}

以上演示所用的項目源碼和JAR包在此處可以獲取

 

4.  安全性考慮

有人可能會問,黑客藉助圖像識別技術,寫一個程序來自動識別圖片裏的驗證碼,不還是能夠攻擊嗎?

看起來是這樣,不過考慮以下幾點:

(1) 專門寫一個程序,購置人工智能的服務器,需要時間和金錢成本。高性能服務器是要花錢的。

(2) 機器識別也需要時間。如果識別的時間較長,則失去攻擊的意義。因爲驗證碼技術不是要禁止別人的訪問,而是讓人訪問得“慢一點”。如果在識別的時候耽誤了時間,過程已經變慢了,此時攻擊的頻率已經顯著下降,已經沒有多大危害了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章