上一篇教程《SSM整合之企業級後臺管理系統(9) - 登錄頁面和登錄跳轉實現》中已經和大家一起學習了使用用戶名和密碼進行登錄,當然,登錄的時候只用用戶名和密碼是不夠滴!爲了安全考慮,還需要加上驗證碼。
一、驗證碼的發展史
驗證碼這個詞最早是在2002年由卡內基梅隆大學的路易斯·馮·安、Manuel Blum、Nicholas J.Hopper以及IBM的John Langford所提出。卡內基梅隆大學曾試圖申請此詞使其成爲註冊商標, 但該申請於2008年4月21日被拒絕。一種常用的CAPTCHA測試是讓用戶輸入一個扭曲變形的圖片上所顯示的文字或數字,扭曲變形是爲了避免被光學字符識別(OCR, Optical Character Recognition)之類的電腦程序自動辨識出圖片上的文數字而失去效果。由於這個測試是由計算機來考人類,而不是標準圖靈測試中那樣由人類來考計算機,人們有時稱CAPTCHA是一種反向圖靈測試。
除了圖形驗證碼之外,爲了無法看到圖像的身心障礙者,替代的方法是改用語音讀出文數字,爲了防止語音辨識分析聲音,聲音的內容會有雜音。當然,本篇博客只討論圖形驗證碼。
總而言之,驗證碼是用來防止惡意登錄、保護賬號安全的。
二、驗證碼登錄的原理
說了這麼多,那我們到底如何編碼實現驗證碼登錄呢?
其實驗證碼的原理並不複雜,實現起來主要有以下幾個步驟:
- 後臺生成驗證碼圖片,同時將驗證碼中內容保存到服務器session中
- 前端登錄時將驗證碼和用戶名、密碼一起提交到後臺
- 後臺驗證前端輸入的驗證碼和session中是否一致,如果一致則驗證成功,進入步驟4;否則驗證不成功,返回步驟1
- 驗證用戶名和密碼,返回登錄驗證信息
- 若登錄驗證成功,則跳轉到相應頁面;否則在登錄頁面提示錯誤信息
當然,一般驗證碼上還有個點擊刷新獲取新驗證碼的功能,其實就是驗證碼圖片上有個點擊事件(onClick),該事件調用後臺獲取驗證碼的方法獲取新的驗證碼,與此同時,後臺session中也更新爲新的驗證碼。
三、整合Kaptcha實現驗證碼校驗登錄
溫馨提示:我們本篇整合Kaptcha教程都是在本系列前面教程的基礎上進行的,如果要完整實現功能的話,建議大家先看下《SSM整合》專欄前面的教程哦(☄⊙ω⊙)☄
1. 首先要pom.xml中加入Kaptchar依賴
<!-- 驗證碼工具 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 在login.jsp中加入驗證碼輸入框和驗證碼展示圖片,驗證碼圖片<img>元素上指定了onclick方法:當點擊驗證碼圖片時調用changeVcode()來刷新驗證碼。
<tr style="width: 100%">
<td style="width: 70%">
<div class="input-group" style="margin-bottom: 15px;width: 100%">
<span class="input-group-addon" id="basic-addon-vcode">
<i class="glyphicon glyphicon-th-large"></i>
</span>
<input class="form-control" name="vcode" id="vcode" type="text" placeholder="請輸入驗證碼" oninput="value=value.replace(/[^a-zA-Z0-9]+$/,'');if(value.length>5)value=value.slice(0,5)">
</div>
</td>
<td style="width: 30%">
<img src='<%=basePath%>/vcode' id="vcode_img" class="vcode_img" onclick="changeVcode($(this));" style="margin-top: -5px;"/>
</td>
</tr>
3. changeVcode()方法。將<img>元素的src屬性指定爲從"/vcode"的Controller獲取,後面加上當前日期時間戳作爲參數的目的是爲了防止瀏覽器緩存驗證碼,這樣就不會引起點擊後驗證碼不刷新的問題。
function changeVcode(obj) {
obj.attr("src", '<%=basePath%>/vcode?d=' + new Date().getTime());
}
4. KaptchaController.java。在controller包中新建KaptchaController.java文件,上面的"/vcode"controller就是寫在這裏,同時還要配置驗證碼的一些屬性。文件代碼如下:
package com.oms.control;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.Properties;
/**
* 登錄驗證碼控制類
*/
@Controller
public class KaptchaController {
@Autowired
private Producer captchaProducer;
/**
* 方法名:生成二維碼控制類
* 創建人:yocco
*/
@RequestMapping(value = "/vcode", method = RequestMethod.GET)
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
// return a jpeg
response.setContentType("image/jpeg");
// create the text for the image
String capText = captchaProducer.createText();
// store the text in the session
request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
// create the image with the text
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
// write the data out
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
return null;
}
/**
* 驗證碼配置
*/
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes");
properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "125");
properties.setProperty("kaptcha.image.height", "45");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); //設定驗證碼的內容範圍,這裏指定只將0-9範圍的數字作爲驗證碼內容
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "宋體,楷體,微軟雅黑");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
上面getKaptcharBean()方法是用來指定驗證碼的一些屬性,比如下面這一句設置了只將0-9的數字作爲驗證碼內容,因此生成的驗證碼裏只會有數字而不會有英文字符或其它內容。
properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
5. login.jsp中的login()方法加上vcode參數傳到後臺進行校驗。並且,當登錄驗證失敗時,調用changeVcode()方法刷新驗證碼。
function login() {
var username = $('#username').val();
var password = $('#password').val();
var vcode = $('#vcode').val();
//判斷輸入是否爲空
if (username == '' || password == '' || vcode == '') {
alert('賬號信息不能爲空!');
return;
}
$.ajax({
type: 'post',
url: '<%=basePath%>/user/checkLogin',
cache: false,
dataType: 'json',
data: {username: username, password: password, vcode: vcode},
success: function (data) {
if (data.code == '0') {
window.location.href = '<%=basePath%>/index';
} else {
changeVcode($('#vcode_img'));
$("#tips").css("visibility", "visible");
$('#tips').text(data.msg);
}
},
error: function (data) {
alert('登錄失敗,請聯繫系統管理員');
}
})
}
6. "/user/checkLogin"controller中加上驗證碼校驗
@ResponseBody
@RequestMapping(value = "/checkLogin", method = RequestMethod.POST)
public ResultObj checkLogin(HttpServletRequest request, HttpServletResponse response) {
logger.info("/user/checkLogin ---> start");
String username = request.getParameter("username");
String password = request.getParameter("password");
String vcode = request.getParameter("vcode");
//校驗驗證碼
if (OmsStringUtils.isNotEmpty(vcode)) {
// 獲取session中的驗證碼
String sessionCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
// 如果輸入的驗證碼和會話的驗證碼不一致的,提示用戶輸入有誤
if (OmsStringUtils.isNotEmpty(sessionCode) && !vcode.equalsIgnoreCase(sessionCode)) {
return new ResultObj("6", "驗證碼錯誤");
}
}
//驗證登錄信息
//驗證用戶名和密碼
//....(本處省略)
logger.info("/user/checkLogin ---> end");
return result;
}
四、本篇結束語
驗證碼登錄並不複雜,只要理清了原理和校驗流程,實現起來就很容易了。不過沒有實現的同學也不要灰心,碰到任何疑問歡迎加羣交流:584017112