最近把 SSM(Spring + Spring MVC + MyBatis) 的 Maven 项目搭建好了,并完成了登录和注册功能。其中注册功能使用 jcaptcha 加入了验证码,并使用 AJAX 完成了基本的验证功能。
本文主要介绍一下 jcaptcha
验证码的实现 (IDEA , 附源码)。
项目源码:
https://github.com/xunge/SSM-jcaptcha
参考:
jcaptcha 官网
IDEA 搭建 SSM
jcaptcha 验证码
由于 jcaptcha 有个缺陷,就是无法使用 AJAX 进行验证,因为一旦验证就会清除 session,这就导致如果使用 AJAX 验证后,如果输入的验证码错误,就无法重复验证,只有刷新网页才可重新使用。
这里参考 这篇博客,将清除 session 的操作提取出来,便可以使用 AJAX 进行验证了。
项目介绍
- 注册页面使用
jcaptcha
实现了验证码功能,并使用AJAX技术实时验证。
- 注册页面的邮箱输入完成,光标移开输入框后,使用AJAX技术到后台数据库进行查找,如果已经注册过则提示该邮箱已被注册。
maven 依赖
<dependency> <groupId>com.octo.captcha</groupId> <artifactId>jcaptcha-all</artifactId> <version>1.0-RC6</version> <exclusions> <exclusion> <groupId>quartz</groupId> <artifactId>quartz</artifactId> </exclusion> <exclusion> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> </exclusion> <exclusion> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> </exclusion> <exclusion> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> </exclusion> <exclusion> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </exclusion> <exclusion> <groupId>concurrent</groupId> <artifactId>concurrent</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId> </exclusion> <exclusion> <groupId>xerces</groupId> <artifactId>xmlParserAPIs</artifactId> </exclusion> </exclusions> </dependency>
|
jcaptcha 配置文件:spring-jcaptcha.xml
在 resources/spring
下新建 spring-jcaptcha.xml
。
该文件主要控制验证码的样式,可根据官网适当修改。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" default-lazy-init="true">
<bean id="captchaService" class="com.xunge.springemp.service.impl.CustomGenericManageableCaptchaService"> <constructor-arg index="0"><ref bean="imageEngine"/></constructor-arg> <constructor-arg index="1"><value>180</value></constructor-arg> <constructor-arg index="2"><value>180000</value></constructor-arg> </bean>
<bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine"> <constructor-arg index="0"> <list> <ref bean="captchaFactory"/> </list> </constructor-arg> </bean>
<bean id="captchaFactory" class="com.octo.captcha.image.gimpy.GimpyFactory"> <constructor-arg> <ref bean="wordgen"/> </constructor-arg> <constructor-arg> <ref bean="wordtoimage"/> </constructor-arg> </bean>
<bean id="wordgen" class= "com.octo.captcha.component.word.wordgenerator.RandomWordGenerator"> <constructor-arg> <value>aabbccddeefgghhkkmnnooppqqsstuuvvwxxyyzz</value> </constructor-arg> </bean>
<bean id="wordtoimage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage"> <constructor-arg index="0"> <ref bean="fontGenRandom"/> </constructor-arg> <constructor-arg index="1"> <ref bean="backGenUni"/> </constructor-arg> <constructor-arg index="2"> <ref bean="decoratedPaster"/> </constructor-arg> </bean>
<bean id="fontGenRandom" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator"> <constructor-arg index="0"> <value>26</value> </constructor-arg> <constructor-arg index="1"> <value>34</value> </constructor-arg> <constructor-arg index="2"> <list> <bean class="java.awt.Font"> <constructor-arg index="0"><value>Arial</value></constructor-arg> <constructor-arg index="1"><value>0</value></constructor-arg> <constructor-arg index="2"><value>32</value></constructor-arg> </bean> </list> </constructor-arg> </bean> <bean id="backGenUni" class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator"> <constructor-arg index="0"> <value>110</value> </constructor-arg> <constructor-arg index="1"> <value>50</value> </constructor-arg> </bean>
<bean id="decoratedPaster" class="com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster"> <constructor-arg type="java.lang.Integer" index="0"> <value>4</value> </constructor-arg> <constructor-arg type="java.lang.Integer" index="1"> <value>4</value> </constructor-arg> <constructor-arg index="2"> <ref bean="colorGen"/> </constructor-arg> <constructor-arg index="3"> <list> </list> </constructor-arg> </bean> <bean id="baffleDecorator" class="com.octo.captcha.component.image.textpaster.textdecorator.BaffleTextDecorator"> <constructor-arg type="java.lang.Integer" index="0"><value>1</value></constructor-arg> <constructor-arg type="java.awt.Color" index="1"><ref bean="colorWrite"/></constructor-arg> </bean> <bean id="colorGen" class="com.octo.captcha.component.image.color.SingleColorGenerator"> <constructor-arg type="java.awt.Color" index="0"> <ref bean="colorBlack"/> </constructor-arg> </bean> <bean id="colorWrite" class="java.awt.Color"> <constructor-arg type="int" index="0"> <value>255</value> </constructor-arg> <constructor-arg type="int" index="1"> <value>255</value> </constructor-arg> <constructor-arg type="int" index="2"> <value>255</value> </constructor-arg> </bean> <bean id="colorBlack" class="java.awt.Color"> <constructor-arg type="int" index="0"> <value>50</value> </constructor-arg> <constructor-arg type="int" index="1"> <value>50</value> </constructor-arg> <constructor-arg type="int" index="2"> <value>50</value> </constructor-arg> </bean> </beans>
|
web.xml 代码
因为是在 resources/spring
下新建 spring-jcaptcha.xml
, 所以 web.xml
无需重新配置。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>spring</display-name>
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/spring-*.xml </param-value> </init-param> </servlet>
<servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
|
JcaptchaImageCreater.java
在 controller
下新建 JcaptchaImageCreater.java
,用来生成验证码图片。
import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException;
import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import com.octo.captcha.service.image.ImageCaptchaService;
@Controller @RequestMapping("/captcha") public class JcaptchaImageCreater {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired private ImageCaptchaService imageCaptchaService;
@RequestMapping public void handleRequest(HttpServletRequest request, HttpServletResponse response) { try { ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); String captchaId = request.getSession().getId(); BufferedImage challenge = imageCaptchaService.getImageChallengeForID(captchaId, request.getLocale());
response.setHeader("Cache-Control", "no-store"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0L); response.setContentType("image/jpeg");
ImageIO.write(challenge, "jpeg", jpegOutputStream); byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
ServletOutputStream respOs = response.getOutputStream(); respOs.write(captchaChallengeAsJpeg); respOs.flush(); respOs.close(); } catch (IOException e) { logger.error("generate captcha image error: {}", e.getMessage()); } }
}
|
CustomGenericManageableCaptchaService.java 重写 GenericManageableCaptchaService.java
在 service
下新建 CustomGenericManageableCaptchaService.java
,将 removeCaptcha 方法提出来,便可以使用 AJAX 进行验证。
import com.octo.captcha.engine.CaptchaEngine; import com.octo.captcha.service.CaptchaServiceException; import com.octo.captcha.service.multitype.GenericManageableCaptchaService;
public class CustomGenericManageableCaptchaService extends GenericManageableCaptchaService{
public CustomGenericManageableCaptchaService(CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize) { super(captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize); }
@Override public Boolean validateResponseForID(String ID, Object response) throws CaptchaServiceException { if (!this.store.hasCaptcha(ID)) { throw new CaptchaServiceException( "Invalid ID, could not validate unexisting or already validated captcha"); } Boolean valid = this.store.getCaptcha(ID).validateResponse(response); return valid; }
public void removeCaptcha(String sessionId){ if(sessionId!=null && this.store.hasCaptcha(sessionId)){ this.store.removeCaptcha(sessionId); } } }
|
LoginController
在 controller
下新建 LoginController
,进行用户注册和检查验证码的方法。
import javax.servlet.http.HttpServletRequest;
import com.octo.captcha.service.image.ImageCaptchaService; import com.xunge.springemp.service.impl.CustomGenericManageableCaptchaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView;
import com.xunge.springemp.dao.UserDAO; import com.xunge.springemp.pojo.User; import com.xunge.springemp.service.IUserService;
@Controller @SessionAttributes("username") public class LoginController {
@Autowired private IUserService userService;
@Autowired private ImageCaptchaService imageCaptchaService;
@Autowired private CustomGenericManageableCaptchaService customGenericManageableCaptchaService;
@Autowired private UserDAO userDAO;
@RequestMapping("userAdd") public ModelAndView doAdd(User user, String captcha, HttpServletRequest request) throws Exception {
Boolean isResponseCorrect = imageCaptchaService.validateResponseForID(request.getSession().getId(), captcha); if (isResponseCorrect) { userDAO.addUser(user); customGenericManageableCaptchaService.removeCaptcha(request.getSession().getId()); ModelAndView mv = new ModelAndView("personal"); return mv; } else { ModelAndView mv = new ModelAndView("register"); return mv; } }
@RequestMapping("/checkCaptcha") public @ResponseBody int checkCaptcha(String captcha, HttpServletRequest request) throws Exception { Boolean isResponseCorrect = imageCaptchaService.validateResponseForID(request.getSession().getId(), captcha);
if (isResponseCorrect == false) { return 0; } else { return 1; } } }
|
前端代码
<input type="text" class="form-control input-lg input_size input-captcha" id="captcha" name="captcha" maxlength="4" placeholder="请输入验证码" /> <img class="img-captcha" src="captcha" onclick="this.src='captcha?d='+new Date().getTime()" />
|
JS 代码
前端 AJAX 验证使用 JQuery 的 validate,进行表单的验证更美观。
$("#regform").validate({ rules: { captcha: { required: true, remote: { url:"checkCaptcha.do", type:"get", contentType: "application/json;charset=utf-8", data:{ captcha:function(){return $("#captcha").val();} }, dataFilter: function(data, type) { if (data == 0) return false; else return true; } } } }, messages: { captcha: { required: "请输入验证码", remote: "验证码错误" } }, });
|
总结
更详细信息可以参考源码。