验证码生成模块,配置信息基本和前面的模块一样。account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户用肉眼读取图片的值,并将验证码的主键与这个值交给服务进行验证。这一服务对应的接口可以定义如下:
具体代码:
public interface AccountCaptchaService { //生成主见 String generateCaptchaKey() throws AccountCaptchaException; byte[] generateCaptchaImage(String captchaKey) throws AccountCaptchaException; //验证主键和值 boolean validateCaptcha(String captchaKey, String captchaValue)throws AccountCaptchaException; List<String> getPreDefinedTexts(); void setPreDefinedTexts(List<String> preDefinedTexts); }
额外定义的getPreDefinedText和set方法可以预定义验证码图片的值,提高程序的可预测性。
为你了能够生成随机的验证码主键,定义一个类RandomGenerator如下:
public class RandomGenerator { private static String rangeString = "0123456789qwertyuiopasdfghjklzxcvbnm"; public static synchronized String getRandomString(){ Random random = new Random(); StringBuffer result = new StringBuffer(); for (int i = 0; i < 8; i++) { result.append(rangeString.charAt(random.nextInt(rangeString.length()))); } return result.toString(); } }
该方法提供了一个线程安全且静态的方法,nextInt()会放回一个大于等于0且小于n的整数。
接口实现:
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())); } public String generateCaptchaKey() throws AccountCaptchaException { String key = RandomGenerator.getRandomString(); String value = getCaptchaText(); captchaMap.put(key, value); return key; } 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("captaha key"+captchaKey+"not found"); } BufferedImage image = producer.createImage(text); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { ImageIO.write(image,"jpg" , out); } catch (Exception e) { throw new AccountCaptchaException("failed to write image"); } return out.toByteArray(); } public boolean validateCaptcha(String captchaKey, String captchaValue) throws AccountCaptchaException { String text = captchaMap.get(captchaKey); if (text == null) { throw new AccountCaptchaException("captaha key"+captchaKey+"not found"); } if (text.equals(captchaValue)) { captchaMap.remove(captchaKey); return true; }else { return false; } } public List<String> getPreDefinedTexts() { return preDefinedTexts; } public void setPreDefinedTexts(List<String> preDefinedTexts) { this.preDefinedTexts = preDefinedTexts; } }
afterPropertiesSet会在framework初始化的时候调用,这个方法初始化验证码生成器,并提供默认配置。
generateCaptchaKey()首先生成一个随机的验证码主键,每个主键将和一个验证码字符串相关联,然后这组关联会被存储到中captchaMap以备将来验证。主键的目的仅仅是标识验证码图片,其本身没有实际的意义。getCaptchaText()用来生成验证码字符串,当preDefinedTexts存在或者为空的时候,就是用验证码图片生成器producer创建一个随机的字符串。当preDefinedTexts,不为空的时候,就顺序地循环该字符串列表读取值。preDefinedTexts有其对应的一组get和stet方法,这样就能让用户预定义验证码字符串的值。generateCaptchaImage方法就能通过producer来生成一个Bufferedlmage ,随后的代码将这个图片对象转换成jpg格式的字节数组并返回。有了该字节数组,用户就能随意地将其保存成文件,或者在网页上显示。
用户得到了验证码图片以及主键后。就会识别图片中所包含的字符串信息,然后将此验证码的值与主键一起反馈给 validateCaptcha方法以进行验证。
测试代码:
public class AccountCaptchaServiceTest { private AccountCaptchaService service; @Before public void prepare() throws Exception{ ApplicationContext ctx = new ClassPathXmlApplicationContext("account_captcha.xml"); service = (AccountCaptchaService) ctx.getBean("accountCaptchaService"); } @Test public void testGenerateCaptcha() throws Exception{ String captchaKey = service.generateCaptchaKey(); assertNotNull(captchaKey); byte[] captchaImage = service.generateCaptchaImage(captchaKey); assertTrue(captchaImage.length>0); File image = new File("target"+captchaKey +".jpg"); OutputStream output = null; try { output = new FileOutputStream(image); output.write(captchaImage); } finally { if (output != null) { output.close(); } } assertTrue(image.exists() && image.length()>0); } @Test 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 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, "67809")); } }
public class RandomGeneratorTest { @Test public void testGetRandomTest() throws Exception{ Set<String> randoms = new HashSet<String>(100); for (int i = 0; i < 100; i++) { String random = RandomGenerator.getRandomString(); assertFalse(randoms.contains(random)); randoms.add(random); } } }
这个测试代码比较容易看懂。运行测试后可以在项目的target目录下看到生成的验证码图片。