Struts2学习笔记(十九) 验证码
概述
验证码也是我们在web应用中经常要用到的功能。基本思路就是,我们在服务端动态的生一成张图片,然后将它输出到客户端。图片上包含一些字符信息,我们将这些字符信息事先保存在session中,那么客户端在看到图片之后,将图片上的字符输出到表单中,然后将表单提交。我们接收到表单数据之后,对表单中提交的验证码与session中保存的验证码进行比对,如果相同,那么验证通过。否则,验证失败!采取一些处理。
验证码的主要作用就是用在用户登录上,能有效的防止客户端多次发送登录请求来暴力破解。由于验证码信息是以图片的形式呈现的,因此要想通过程序来识别这些字符还是不太容易的。当然验证码也不是绝对安全的,但是相对来说它的实现比较容易,安全性也相对较高,所以使用的非常广泛。
使用jsp/servlet实现图片验证码
这里我们主要是学习验证码的实现原理,因此就不做的过于复杂了。其实有很多开源的验证码生成库,他们做的比较精密,集成也比较方便,因此有需要的话可以使用第三方的库。为了生成验证码的部分代码能够被重用,我将它单独提取成为一个类。
public class AuthCode { private ByteArrayInputStream input; private ByteArrayOutputStream output; private String code;// 验证码 private int codeNum;// 验证码字符数量 private int width; private int height; // 构造器 private AuthCode(int width, int height, int codeNum) { this.width = width; this.height = height; this.codeNum = codeNum; if (width < 15 * codeNum + 6) { this.width = 13 * codeNum + 6; } if (height < 20) { this.height = 20; } buildImage(); } // 以字符串形式返回验证码 public String getCode() { return code; } // 以输入流的形式返回验证图片 public ByteArrayInputStream getIamgeAsInputStream() { return input; } // 以输出流的形式返回验证图片 public ByteArrayOutputStream getImageAsOuputStream() { return output; } // 创建默认大小的验证码 public static AuthCode createInstance() { return new AuthCode(85, 20, 4); } // 创建指定大小的验证码 public static AuthCode createInstance(int width, int height, int codeNum) { return new AuthCode(width, height, codeNum); } // 生成验证码图片 private void buildImage() { BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 获取图形上下文 Graphics g = image.getGraphics(); // 生成随机类 Random random = new Random(); // 设定背景色 g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); // 设定字体 g.setFont(new Font("Times New Roman", Font.PLAIN, 18)); // 随机产生150条干扰线,使图象中的认证码不易被其它程序探测到 g.setColor(getRandColor(160, 200)); for (int i = 0; i < 150; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } // 取随机产生的认证码 String codes = "ABCDEFGHJKLMNOPQRSTUVWXYZ23456789"; String sRand = ""; for (int i = 0; i < codeNum; i++) { String rand = codes.charAt(random.nextInt(codes.length())) + ""; sRand += rand; // 将认证码显示到图象中 g.setColor(new Color(20 + random.nextInt(110), 20 + random .nextInt(110), 20 + random.nextInt(110))); // 将字符串绘制到图片上 g.drawString(rand, i * (width / codeNum) + 6, (int)((height+12)/2)); } /* 验证码赋值 */ this.code = sRand; // 图象生效 g.dispose(); try { output = new ByteArrayOutputStream(); ImageOutputStream imageOut = ImageIO .createImageOutputStream(output); ImageIO.write(image, "JPEG", imageOut); imageOut.close(); input = new ByteArrayInputStream(output.toByteArray()); } catch (Exception e) { System.out.println("验证码图片产生出现错误:" + e.toString()); } } // 获取随机颜色 private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > 255) fc = 255; if (bc > 255) bc = 255; int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
然后我们定义一个Servlet专门用于输出验证码:
public class AuthCodeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AuthCode code = AuthCode.createInstance(); req.getSession().setAttribute("authCode", code.getCode()); resp.getOutputStream() .write(code.getImageAsOuputStream().toByteArray()); } }
index.jsp
<html> <head> <title>Auth Code</title> <script type="text/javascript"> function changeImage(obj){ obj.src = "AuthCodeServlet?="+Math.random(); } </script> </head> <body> <form action="LoginServlet" method="post"> username: <input type="text" name="userName" /> <br> password: <input type="password" name="password" /> <br /> authcode: <input type="text" name="authCode" /> <img src="AuthCodeServlet" onclick="changeImage(this)" /> <br> <input type="submit" value="submit" /> </form> </body> </html>
LoginServlet.java
public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); String password = req.getParameter("password"); String authCode = req.getParameter("authCode"); String code = (String) req.getSession().getAttribute("authCode"); if (authCode != null && authCode.toUpperCase().equals(code) && "hello".equals(userName) && "world".equals(password)) { resp.getWriter().println("login success! authCode:"+authCode); }else{ resp.getWriter().println("login failed! authCode:"+authCode); } } }
测试:
使用Struts2实现图片验证码
Struts2中有一种名为”stream”类型的Result,我们在学习文件下载的时候已经学习过了。我们已经知道只要我们给它提供一个输入流,只需要设置好相应的ContentType,那么它就会自动将这个输入流输出到浏览器中。那么在这里我们依然可以利用它的这个特性。在上一个例子中的验证码生成类中,提供了一个获取输入流的方法,因此这里我还是使用上面的验证码生成类AuthCode。
AuthCodeAction.java
public class AuthCodeAction extends ActionSupport implements SessionAware { private Map<String, Object> session; public void setSession(Map<String, Object> session) { this.session = session; } public InputStream getInputStream() { AuthCode code = AuthCode.createInstance(); session.put("authCode", code.getCode()); return code.getIamgeAsInputStream(); } @Override public String execute() throws Exception { return SUCCESS; } }
LoginAction.java
public class LoginAction extends ActionSupport implements SessionAware { private String userName; private String password; private String authCode; private Map<String, Object> session; public void setSession(Map<String, Object> session) { this.session = session; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getAuthCode() { return authCode; } public void setAuthCode(String authCode) { this.authCode = authCode; } public Map<String, Object> getSession() { return session; } @Override public String execute() throws Exception { String code = (String) session.get("authCode"); if (authCode != null & authCode.toUpperCase().equals(code)) { return SUCCESS; } return INPUT; } }
index.jsp基本保持不变,略了。
struts.xml
<package name="default"namespace="/"extends="struts-default">
<action name="authcode" class="action.AuthCodeAction">
<result name="success" type="stream">
<param name="contentType">image/jpeg</param>
</result>
</action>
<action name="login" class="action.LoginAction">
<result>success.jsp</result>
<result name="input">index.jsp</result>
</action>
</package>
测试:
验证码这块相对来说还算是比较简单。但是如果要做一些比较复杂的验证码的话还是要花不少心思的。这块本来跟Struts2没多大关系,不过既然刚好学到了,也就归如到Struts2中吧。
如有不妥之处,还请路过的老鸟们指正!小弟感激不尽。