首先声名转载地址,表示对作者的尊重:http://hongtaq.iteye.com/blog/1481521
Struts2中显示图片验证码
现在很多网站在进行注册、登录甚至发表文章都要求输入验证码,只有用户输入的验证码和验证码输入框旁边的小图片中显示的文字相同时,相关的操作(注册、登录等)才可以继续进行。这其实是网站用于防犯用户使用注册机进行恶意攻击的一种必手段。
其实前面在学习struts基本原理的时候,偶在脑子里就曾经冒出过这个问题:用struts2如何输出一个验证码?
然后又google和baidu了一下,发现网上资源的确不少,但把原理讲透了的不多。而其中有一篇被转了很多次的帖子最可恨,它号称支持中文的验证码,但是里面用到了几个自定义的类,而帖子里根本没有公布这些类的源代码,也没有告诉你类到哪里可以下载到。
不过还好,struts2里提供了一些demo和源代码,通过阅读这些demo源代码,可以找到解决这个问题的方法。
回顾一下struts2的基本流程:
1. 客户端发出请求
2. struts2过滤器拦截请求
3. 过滤器创建Action对象
4. 调用Action对象的相应方法
5. 根据方法返回的字符串值跳到相应的资源(jsp, html等)
从这几个步骤看,似乎无从下手。Struts2最终都会把资源重定向到一个jsp或html页面,难道我们只能在jsp里做手脚吗?
其实在struts的配置文件中,result元素还有一个名为type的属性。缺省情况下,它表示一个result是用于跳转到一个指定的url,如果给它指定一个值,则输出到客户端的可以是其它的类型。思路就从这里打开。
Result元素可以将类型指定为实现了com.opensymphony.xwork2.Result;接口的类。这个接口中定义了一个方法:
execute
(ActionInvocation invocation)
struts2就是在这个方法中完成展示给客户端内容的工作。
OK,其实说白了,原理也就是这么简单,要想完成输出图片验证码的操作,与一般的struts2不同的地方有:
1、 在Action中创建要显示的字符串,然后在内存中创建Image对象,并把字符串打印到这个Image对象中,再把Image对象放到一个字节数组中。当然别忘了把字符串的值放到Session中
2、 编写一个类并实现com.opensymphony.xwork2.Result接口,在这个类中将Action对象创建的字节数组写到输出流中。
3、 在配置文件中把 result 的类型指定为前面编写的Result类型。
源代码如下:
1、 Action代码:
package stu.struts2.image;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Random;
import java.awt.*;
import java.awt.image.*;
import java.io.ByteArrayOutputStream;
import org.apache.struts2.ServletActionContext;
public class ImageAction extends ActionSupport {
/**
* 验证码对应的Session名
*/
private static final String SessionName = "CheckCodeImageAction";
/**
* 用于随机生成验证码的数据源
*/
private static final char[] source = new char[]{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
};
/**
* 用于随机打印验证码的字符颜色
*/
private static final Color[] colors = new Color[]{
Color.RED, Color.BLUE, Color.BLACK
};
/**
* 用于打印验证码的字体
*/
private static final Font font = new Font("宋体", Font.PLAIN, 13);
/**
* 用于生成随机数的随机数生成器
*/
private static final Random rdm = new Random();
private String text = "";
private byte[] bytes = null;
private String contentType = "image/png";
public byte[] getImageBytes(){
return this.bytes;
}
public String getContentType(){
return this.contentType;
}
public void setContentType(String value){
this.contentType = value;
}
public int getContentLength(){
return bytes.length;
}
/**
* 生成长度为4的随机字符串
*/
private void generateText(){
char[] source = new char[4];
for(int i=0; i<source.length; i++){
source[i] = ImageAction.source[rdm.nextInt(ImageAction.source.length)];
}
this.text = new String(source);
// 设置Session
ServletActionContext.getRequest().getSession().setAttribute(SessionName,this.text);
}
/**
* 在内存中生成打印了随机字符串的图片
* @return 在内存中创建的打印了字符串的图片
*/
private BufferedImage createImage(){
int width = 35;
int height = 14;
BufferedImage image =
new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, width, height);
g.setFont(font);
for(int i=0; i<this.text.length(); i++){
g.setColor(colors[rdm.nextInt(colors.length)]);
g.drawString(this.text.substring(i, i+1), 2 + i * 8, 12);
}
g.dispose();
return image;
}
/**
* 根据图片创建字节数组
* @param image 用于创建字节数组的图片
*/
private void generatorImageBytes(BufferedImage image){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try{
javax.imageio.ImageIO.write(image, "jpg", bos);
this.bytes = bos.toByteArray();
}catch(Exception ex){
}finally{
try{
bos.close();
}catch(Exception ex1){
}
}
}
/**
* 被struts2过滤器调用的方法
* @return 永远返回字符串"image"
*/
public String doDefault(){
this.generateText();
BufferedImage image = this.createImage();
this.generatorImageBytes(image);
return "image";
}
}
2、 Result 类
package stu.struts2.image;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
public class ImageResult implements Result {
public void execute(ActionInvocation ai) throws Exception {
ImageAction action = (ImageAction)ai.getAction();
HttpServletResponse response = ServletActionContext.getResponse();
response.setHeader("Cash", "no cash");
response.setContentType(action.getContentType());
response.setContentLength(action.getContentLength());
response.getOutputStream().write(action.getImageBytes());
response.getOutputStream().flush();
}
}
3、 struts.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts SYSTEM "struts-2.1.dtd" >
<struts>
<include file="/struts-default.xml" />
<package namespace="/test" name="testPackage" extends="struts-default">
<result-types>
<result-type name="ValidateImage" class="stu.struts2.image.ImageResult" />
</result-types>
<action name="ValidateImage" class="stu.struts2.image.ImageAction"method="doDefault">
<result name="image" type="ValidateImage" />
</action>
</package>
</struts>
总结一下
顺着Result的type属性这个思路往下想,result其实应该还有很多种类型可以指定(实现了com.opensymphony.xwork2.Result接口),那么缺省时会是哪种类型呢?
用解压缩软件打开struts2-core-2.1.6.jar,提取里面的 struts-default.xml文件,我们会发现其中有一段
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
<result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
<result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/>
<result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
<result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>
<result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
<result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>
<result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
<result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />
</result-types>
这是struts-default中预定义的result类型,我估计缺省的应该就是ServletRedirectResult或者ServletActionRedirectResult。如果想进一步研究,我们还可以去阅读这两个类的源代码。