验证码一般用于登录功能,其实现一般分为两种,js验证码生成和服务器验证码生成。
js的生成也就是使用js的2D功能画出验证码,这种方法实现起来只要调用几个js插件就可以快速生成,但缺点是,如果有人通过更改浏览器js来绕过验证,这验证码的防线就是一种摆设了。
至于服务器的验证码生成要安全的多。其原理也不复杂。
1.在打开登录页面时,通过加载图片元素标签img的src属性可再发出一个请求到后台。
2.在这个请求中可以在服务器中生成一个随机验证字符串并放置于session中。
3.利用java中的2d功能把字符串生成一个图片二进制,并在图片中加入杂波和着色等等。
4.请求返回图片流输出时,把生成后的图片二进制流输出即可。
5.登录验证时,将请求参数中的验证码与session中保存的验证时进行比较验证,无论验证成功与否均在方法最后清除session中的验证码。
开始编码,就以上面的步骤来一步步的写
1.先写html页面
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>token</title>
<script type="text/javascript">
function changeImage(imageComponent){
var time = new Date().getTime();
imageComponent.src = "${pageContext.request.contextPath}/characters.jpg?" + time;
}
</script>
</head>
<body>
<form method="post" action="next.jsp">
username:<input type="text" name="username" />
password:<input type="password" name="password" />
<input type="text" name="token" />
<input type="submit" value="login" />
<img οnclick="changeImage(this)" src="${pageContext.request.contextPath}/characters.jpg" />
</form>
</body>
</html>
这里可以看到,在这个页面中有三个输入框,一个用户名,一个密码,还有一个验证码。最后还有一个图片标签。地址就是要生成验证码图片的请求。
加入了一个js方法,就是在点击验证码图片时可以换一个验证码。
这个不用讲,没什么好说的。
2.要处理请求就要先给个请求的处理机制。这里用xml配个servlet来解决
<servlet> <servlet-name>ImageTokenServlet</servlet-name> <servlet-class>guides.servlet.imgtoken.ImageTokenServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ImageTokenServlet</servlet-name> <url-pattern>/characters.jpg</url-pattern> </servlet-mapping>
再来看看ImageTokenServlet是怎么来处理的
public class ImageTokenServlet extends HttpServlet {
private int width = 80;
private int height = 30;
private String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private int length = 4;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//产生tokens并将其保存在当前的会话中
TokenUtil.saveToken(request, characters, length);
//生成图片响应
TokenUtil.generateTokenImage(response, request.getSession(), width, height);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doGet(request, response);
}
}
}
这里可以看出,servlet也只做了两件事,就是调用了两个工具类方法。至于最上面的属性从名子上就能看出他们的含义了,不解释。
再来看TokenUtil.saveToken(request, characters, length);的实现
public static void saveToken(HttpServletRequest request, String characters, int length) {
Random ran = new Random();
char[] tokens = new char[length];
for (int i = 0; i < length; i++) {
char ch = characters.charAt(ran.nextInt(characters.length()));
tokens[i] = ch;
}
HttpSession session = request.getSession();
session.setAttribute("token", new String(tokens));
}
到目前为止,随机的验证码已经生成了
3.看看是怎么把字符串生成一个图片二进制流的
public static void generateTokenImage(HttpServletResponse response, HttpSession session, int width, int height) throws IOException {
//设置响应内容为图片格式
response.setContentType("image/jpeg");
//禁止浏览器缓存
response.addHeader("pragma", "NO-cache");
response.addHeader("Cache-Control", "no-cache");
response.addDateHeader("Expries", 0);
//绘制图片
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
//以下填充背景颜色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
//设置字体颜色
g.setColor(Color.RED);
Font font = new Font("Arial", Font.BOLD, 18);
g.setFont(font);
//绘制
String token = (String) session.getAttribute("token");
g.drawString(token, 5, height - 2);
g.dispose();
//发送内容到客户端
ServletOutputStream out = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
}
注意,这里不仅是把图片生成了,也同时使用了response的输出流把图片给输出了。
4.输入功能已经在上一步给完成了。。。
5.登录验证时,再写一个工具方法即可
public static boolean isTokenValid(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
boolean isLegal = true;
String[] tokenParams = (String[]) request.getParameterValues("token");
if (tokenParams == null) {
isLegal = false;
} else {
String token = tokenParams[0];
if (token == null) {
isLegal = false;
} else {
String sessionToken = (String) session.getAttribute("token");
if (!token.equalsIgnoreCase(sessionToken)) {
isLegal = false;
}
}
}
//删除会话中的token信息
session.removeAttribute("token");
return isLegal;
}
这个工具类方法在登录验证时调用一下查看一下返回值即可
OK了,整个流程的核心就是这么多。
把servlet类和工具类再整理一下,完整点重新贴出来。
package guides.servlet.imgtoken;
import guides.servlet.util.TokenUtil;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 用来以图片的形式来生成验证码。
* 生成验证码后先将其保存到用户的当前会话中,然后在以图片的形式发送到客户端。
* 验证码的验证要通过TokenFilter来完成。
* 该Servlet包含如下的初始化参数,来定制验证码的信息:
* <ul>
* <li>characters:生成验证码所有的字符序列,默认为数字和字母</li>
* <li>length:生成的验证码的长度,默认为4位</li>
* <li>width:验证码图片的显示宽度,默认为80像素</li>
* <li>height:验证码图片的高度,默认为30像素</li>
* </ul>
*
* 该Servlet需要在应用的web.xml进行如下部署:
* <servlet>
* <servlet-name>ImageTokenServlet</servlet-name>
* <servlet-class>guides.servlet.imgtoken.ImageTokenServlet</servlet-class>
* </servlet>
* <servlet-mapping>
* <servlet-name>ImageTokenServlet</servlet-name>
* <url-pattern>/token/image.jpg</url-pattern>
* </servlet-mapping>
*
* 然后在客户端网页中进行如下引用:
* <img src="/yourContextPath/token/image.jpg"></img>
*/
@SuppressWarnings("serial")
public class ImageTokenServlet extends HttpServlet {
private int width = 80;
private int height = 30;
private String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private int length = 4;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//产生tokens并将其保存在当前的会话中
TokenUtil.saveToken(request, characters, length);
//生成图片响应
TokenUtil.generateTokenImage(response, request.getSession(), width, height);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doGet(request, response);
}
public void init() {
String initParam = getInitParameter("characters");
if (initParam != null) {
this.characters = initParam;
}
initParam = getInitParameter("length");
if (initParam != null) {
this.length = Integer.parseInt(initParam);
}
initParam = getInitParameter("width");
if (initParam != null) {
this.width = Integer.parseInt(initParam);
}
initParam = getInitParameter("height");
if (initParam != null) {
this.height = Integer.parseInt(initParam);
}
}
}
package guides.servlet.util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 验证码处理工具类,包含如下功能:
* 1)产生验证码
* 2)保存验证码
* 3)校验验证码
* 4)生成图像验证码
*/
public class TokenUtil {
public static final String TOKEN_KEY_NAME = "token";
public static final String TOKEN_PARAMETER_NAME = "token";
/**
* 根据给定的字符序列生成随即的验证码。
*
* @param characters 验证码的字符取值范围
* @param length 生成的验证码的长度
* @return 指定长度的验证码字符串
*/
public static String generateToken(String characters, int length) {
Random ran = new Random();
char[] tokens = new char[length];
for (int i = 0; i < length; i++) {
char ch = characters.charAt(ran.nextInt(characters.length()));
tokens[i] = ch;
}
return new String(tokens);
}
/**
* 生成验证码,并将其保存到当前的会话中。该方法主要应用于Struts1,Servlet,JSP环境。
* 如果会话不存在,自动创建会话。
*
* @param session 当前的请求对象。
* @param characters 验证码的字符取值范围
* @param length 生成的验证码的长度
*/
public static void saveToken(HttpServletRequest request, String characters, int length) {
HttpSession session = request.getSession();
session.setAttribute(TOKEN_KEY_NAME, generateToken(characters, length));
}
/**
* 针对当前的请求进行验证码的校验,并且在校验完毕候从会话中清空验证码,将其作废。
* 该方法适用于Struts1,Servlet,JSP环境。
*
* 下面的情况标识校验通过:
* 1)请求中包含名字为TokenConstants.TOKEN_PARAMETER_NAME所指定的参数。
* 2)会话中包含名字为TokenConstants.TOKEN_KEY_NAME所指定的属性。
* 3)1参数的值和2属性的值(字符串类型)相等(忽略大小写)。
*
* @param request HTTP请求对象。
* @return 验证通过返回true,否则返回false。
*/
public static boolean isTokenValid(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
boolean isLegal = true;
String[] tokenParams = (String[]) request.getParameterValues(TOKEN_PARAMETER_NAME);
if (tokenParams == null) {
isLegal = false;
} else {
String token = tokenParams[0];
if (token == null) {
isLegal = false;
} else {
String sessionToken = (String) session.getAttribute(TOKEN_KEY_NAME);
if (!token.equalsIgnoreCase(sessionToken)) {
isLegal = false;
}
}
}
//删除会话中的token信息
session.removeAttribute(TOKEN_KEY_NAME);
return isLegal;
}
/**
* 向客户端生成验证码图片。
*
* @param response HHTP 响应对象。
* @param session 包含会话属性信息的Map对象。
* @param width 验证码图片的宽度。
* @param height 验证码图片的高度。
* @throws IOException 执行操作时发生网络错误时。
*/
public static void generateTokenImage(HttpServletResponse response, HttpSession session, int width, int height) throws IOException {
//设置响应内容为图片格式
response.setContentType("image/jpeg");
//禁止浏览器缓存
response.addHeader("pragma", "NO-cache");
response.addHeader("Cache-Control", "no-cache");
response.addDateHeader("Expries", 0);
//绘制图片
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
//以下填充背景颜色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
//设置字体颜色
g.setColor(Color.RED);
Font font = new Font("Arial", Font.BOLD, 18);
g.setFont(font);
//绘制
String token = (String) session.getAttribute(TOKEN_KEY_NAME);
g.drawString(token, 5, height - 2);
g.dispose();
//发送内容到客户端
ServletOutputStream out = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
}
}