上一篇教程《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="请输入验证码" οninput="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" οnclick="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