您的位置:首页 > 其它

一个Maven实现的验证码模块

2015-07-24 23:08 316 查看
下面是Maven构建的实现账户注册服务的account-captcha模块,该模块负责处理账户注册时key生成、图片生成以及验证等。

POM部分配置

//account-captcha的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>account-captcha</artifactId>
<name>Account Captcha</name>

<properties>
<kaptcha.version>2.3.2</kaptcha.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.code.kaptcha</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build/>
</project>

这里的kaptcha依赖通过 默认repo没有找到,所以只有通过手动添加。jar下载包地址:https://code.google.com/p/kaptcha/downloads/list

另外手动jar包到本地仓库的教程地址:http://www.cnblogs.com/jerome-rong/archive/2012/12/08/2808947.html

简单来说这里的kaptcha包下载jar包之后执行命令:mvn install:install-file -Dfile=${下载路径}\kaptcha-2.3.2.jar -DgroupId=com.google.code.kaptcha -DartifactId=kaptcha -Dversion=2.3.2 -Dpackaging=jar  即可添加到本地仓库。

这段POM配置中首先是父模块声明,之后是项目本身的artifactId和name,groupId和version继承自父模块。然后声明了一个Maven属性kaptcha.version,用于依赖声明。依赖除了SpringFramework和junit之外,还包含一个com.google.code.kaptcha:kaptcha。kaptcha是一个用来生成验证码的开源类库。

注意不要忘记把account-captcha加入到聚合模块中,即修改account-parent的POM文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Account Parent</name>

<modules>
<module>../account-email</module>
<module>../account-persist</module>
<module>../account-captcha</module>
</modules>

<properties>
<springframework.version>4.1.7.RELEASE</springframework.version>
<junit.version>4.12</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>


主代码部分
account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户读取图片值并交给服务验证。这一服务的接口定义如下:
//AccountCaptchaService.java
package com.juvenxu.mvnbook.account.captcha;

import java.util.List;

public interface AccountCaptchaService {
/**
* 生成随机的验证码主键
*
* @return 验证码主键
* @throws AccountCaptchaException
*/
String generateCaptchaKey() throws AccountCaptchaException;

/**
* 生成验证码图片
*
* @param 验证码主键
* @return 验证码图片
* @throws AccountCaptchaException
*/
byte[] generateCaptchaImage(String captchaKey)
throws AccountCaptchaException;

/**
* 验证用户反馈的主键和值
*
* @param captchaKey
*            验证码主键
* @param captchaValue
*            验证码的值
* @return 是否验证成功
* @throws AccountCaptchaException
*/
boolean validateCaptcha(String captchaKey, String captchaValue)
throws AccountCaptchaException;

/**
* 预获取验证码内容
*
* @return 验证码
*/
List<String> getPreDefinedTexts();

/**
* 预定义验证码图片的内容
*
* @param 验证码
*/
void setPreDefinedTexts(List<String> preDefinedTexts);
}


这里引入了一个异常类,定义如下:
package com.juvenxu.mvnbook.account.captcha;

@SuppressWarnings("serial")
public class AccountCaptchaException extends Exception {
/**
* 带一个参数的构造函数
*
* @param message
*            错误信息
*/
public AccountCaptchaException(String message) {
super(message);
}

/**
* 带两个参数的构造参数
*
* @param message
*            错误信息
* @param throwable
*            是否可抛出
*/
public AccountCaptchaException(String message, Throwable throwable) {
super(message, throwable);
}
}
这里的服务接口之所以要定义额外的getPreDefinedTexts()和setPreDefinedTexts()方法,主要是为了提高可测试性,方便获取验证码及设置方便测试。
另外为了能够生成随机的验证码主键,引入一个RandomGenerator类
//RandomGenerator.java
package com.juvenxu.mvnbook.accoun
4000
t.captcha;

import java.util.Random;

public class RandomGenerator {
// 验证码字符范围
private static String range = "0123456789abcdefghijklmnopqrstuvwxyz";

/**
* 静态且安全地获取一个长度为8的随机字符串
*
* @return 随机字符串
*/
public static synchronized String getRandomString() {
Random random = new Random();
StringBuffer result = new StringBuffer();
for (int i = 0; i < 8; i++) {
result.append(range.charAt(random.nextInt(range.length())));
}
return result.toString();
}
}
接下来就是模块的重要部分,服务的实现
package com.juvenxu.mvnbook.account.captcha;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.imageio.ImageIO;

import org.springframework.beans.factory.InitializingBean;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;

public class AccountCaptchaServiceImpl implements AccountCaptchaService,
InitializingBean {
private DefaultKaptcha producer; // 验证码生成器
private Map<String, String> captchaMap = new HashMap<String, String>(); // 验证键值对
private List<String> preDefinedTexts; // 预定义验证码字符串
private int textCount = 0; // 验证码计数器

public void afterPropertiesSet() throws Exception {
producer = new DefaultKaptcha(); // 初始化验证码生成器
producer.setConfig(new Config(new Properties())); // 为producer提供默认配置
}

public String generateCaptchaKey() {
String key = RandomGenerator.getRandomString(); // 生成随机的验证码主键
String value = getCaptchaText();
captchaMap.put(key, value); // 存储主键到captchaMap
return key;
}

public List<String> getPreDefinedTexts() {
return preDefinedTexts;
}

public void setPreDefinedTexts(List<String> preDefinedTexts) {
this.preDefinedTexts = preDefinedTexts;
}

private String getCaptchaText() {
if (preDefinedTexts != null && !preDefinedTexts.isEmpty()) {
String text = preDefinedTexts.get(textCount);
textCount = (textCount + 1) % preDefinedTexts.size();
return text;
} else {
return producer.createText();
}
}

public byte[] generateCaptchaImage(String captchaKey)
throws AccountCaptchaException {
String text = captchaMap.get(captchaKey);
if (text == null) {
throw new AccountCaptchaException("Captch key '" + captchaKey
+ "' not found!");
}
// 通过producer生成一个BufferImage
BufferedImage image = producer.createImage(text);
// 将图片对象转换为jpg格式的字节数组并返回
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(image, "jpg", out);
} catch (IOException e) {
throw new AccountCaptchaException(
"Failed to write captcha stream!", e);
}
return out.toByteArray();
}

public boolean validateCaptcha(String captchaKey, String captchaValue)
throws AccountCaptchaException {
String text = captchaMap.get(captchaKey); // 通过主键找到正确的验证码值
if (text == null) {
throw new AccountCaptchaException("Captch key '" + captchaKey
+ "' not found!");
}
if (text.equals(captchaValue)) { // 将验证码的值与用户输入值进行比较
captchaMap.remove(captchaKey);
return true;
} else {
return false;
}
}
}
另外还需要SpringFramework的配置文件,放在src/main/resources/目录下
//account-captcha.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-4.1.xsd"> <bean id="accountCaptchaService"
class="com.juvenxu.mvnbook.account.captcha.AccountCaptchaServiceImpl">
</bean>
</beans>

测试代码
首先是测试随机数生成:
//RandomGeneratorTest.java
package com.juvenxu.mvnbook.account.captcha;

import static org.junit.Assert.assertFalse;

import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

public class RandomGeneratorTest {
@Test
public void testGetRandomString() throws Exception {
Set<String> randoms = new HashSet<String>(100);	//创建初始容量为100的集合
for (int i = 0; i < 100; i++) {
String random = RandomGenerator.getRandomString();
assertFalse(randoms.contains(random));		//检查新生成的随机数是否包含在集合中
randoms.add(random);
}
}
}
然后是服务模块的测试:
//AccountCaptchaServiceTest.java
package com.juvenxu.mvnbook.account.captcha;

import static org.junit.Assert.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountCaptchaServiceTest {
private AccountCaptchaService service;

@Before
/**
* 运行在测试方法前,初始化AccountCaptchaService的bean
* @throws Exception
*/
public void prepare() throws Exception {
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"account-captcha.xml");
service = (AccountCaptchaService) ctx.getBean("accountCaptchaService");
}

@Test
/**
* 测试验证码图片生成
* @throws Exception
*/
public void testGenerateCaptcha() throws Exception {
String captchaKey = service.generateCaptchaKey();
assertNotNull(captchaKey);

byte[] captchaImage = service.generateCaptchaImage(captchaKey);
assertTrue(captchaImage.length > 0);
//在项目的target目录下创建一个名为主键的jpg格式文件
File image = new File("target/" + captchaKey + ".jpg");
OutputStream output = null;
try {
//将验证码图片字节数组内容写入到jpg文件中
output = new FileOutputStream(image);
output.write(captchaImage);
} finally {
if (output != null) {
output.close();
}
}
//检查文件存在且包含实际内容
assertTrue(image.exists() && image.length() > 0);
}

@Test
/**
* 测试验证流程正确性
* @throws Exception
*/
public void testValidateCaptchaCorrect() throws Exception {
List<String> preDefinedTexts = new ArrayList<String>();
preDefinedTexts.add("12345");
preDefinedTexts.add("abcde");
service.setPreDefinedTexts(preDefinedTexts);

String captchaKey = service.generateCaptchaKey();
service.generateCaptchaImage(captchaKey);
assertTrue(service.validateCaptcha(captchaKey, "12345"));

captchaKey = service.generateCaptchaKey();
service.generateCaptchaImage(captchaKey);
assertTrue(service.validateCaptcha(captchaKey, "abcde"));
}

@Test
/**
* 测试用户反馈Captcha错误时发生情况
* @throws Exception
*/
public void testValidateCaptchaIncorrect() throws Exception {
List<String> preDefinedTexts = new ArrayList<String>();
preDefinedTexts.add("12345");
service.setPreDefinedTexts(preDefinedTexts);

String captchaKey = service.generateCaptchaKey();
service.generateCaptchaImage(captchaKey);
assertFalse(service.validateCaptcha(captchaKey, "67890"));
}
}


测试结果
执行mvn test,构建成功,结果如下:
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 13.555 s

[INFO] Finished at: 2015-07-24T23:06:14+08:00

[INFO] Final Memory: 12M/127M

[INFO] ------------------------------------------------------------------------

参考书籍:《Maven实战》第10章——徐晓斌著
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  maven 插件 验证码