转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6134649.html
另:算术验证码生成的JSP、Servlet实现均已移植github:https://github.com/ygj0930/CheckCode-in-JSP-Servlet
大家给我个star呀~
在常见的登录功能实现中,单靠账户、密码登录很容易遭受恶意攻击,有些人可以通过写一些脚本自动输入账户密码(当然,是瞎蒙的)频繁登录从而占用服务器的处理资源。这时候,此时,就可以通过验证码来达到拦截“非人类”发出请求。
验证码:全自动区分计算机和人类的图灵测试的缩写,是一种区分用户是计算机的公共全自动程序,这个问题可以由计算机生成并评判,但是必须只有人类才能解答.可以防止恶意破解密码、刷票、论坛灌水、有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录。
实现原理:在服务器的servlet中随机生成一个验证码,一般为四位数字、字母,然后把该验证码保存到session作为一个Attribute,然后通过Java的绘图类以图片的形式把该验证码写到浏览器,并设置给Img标签以图片的形式显示出来。最后,在用户提交数据的时候,在服务器端将用户提交的验证码和session中保存的验证码属性值进行比较,并发回验证结果。(如:验证成功则跳转、不成功则发回错误码重新输入)
普通图片验证码
代码实现:
前端:用一个img标签显示验证码图片,一个输入框接收用户输入的验证码。
<script> function show(o){ //单击图片,重新获得验证码。random的作用是用来修改img图片的来源的,如果没有随机数后缀,则说明没有修改src,浏览器会从缓存中直接读取上一次根据此src获取到的内容进行显示 o.src = "checkCode.jsp?"+Math.random(); } </script> <form method="post" action="reg_do.jsp"> 请输入验证码: <input type="text" name="randomCode_Client" /> <br/><br/> <img src="checkCode.jsp" οnclick="show(this)" /> <br/><br/> </form>
后台:处理登录请求,校对验证码
//得到客户端传入的验证码参数 String randomCode_client=(String)request.getParameter("randomCode_Client"); //得到session上的验证码 String randomCode_server=(String)session.getAttribute("randomCode"); if(!randomCode_client.equals(randomCode_server)){ //验证码错误做出的响应 }else{ //验证码正确做出的响应 }
后台:生成验证码图片并设置到session中,并把图片输出到浏览器显示
int width = 62, height = 22;//定义验证码图片的大小 BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);//在内存中创建图象 Graphics2D g = buffImg.createGraphics();//为内存中要创建的图像生成画布,用于“作画” //画一个白色矩形,作为验证码背景 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); //画一个黑色矩形边框 g.setColor(Color.BLACK); g.drawRect(0, 0, width - 1, height - 1); //画40条灰色的随机干扰线 g.setColor(Color.GRAY); Random random = new Random(); //设置随机种子 for (int i = 0; i < 40; i++) { //设置40条干扰线 int x1 = random.nextInt(width); int y1 = random.nextInt(height); int x2 = random.nextInt(10);//返回0到10之间一个随机数 int y2 = random.nextInt(10); g.drawLine(x1, y1, x1 + x2, y1 + y2); } //创建字体 Font font = new Font("Times New Roman", Font.PLAIN, 18); g.setFont(font); int length = 4; //设置默认生成4个长度的验证码 StringBuffer randomCode = new StringBuffer(); for (int i = 0; i < length; i++) { //取得4位数的随机字符串 String strRand = String.valueOf(random.nextInt(10));//返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值 int red = random.nextInt(255); int green = random.nextInt(255); int blue = random.nextInt(255); g.setColor(new Color(red, green, blue)); //获得一个随机红蓝绿的配合颜色 g.drawString(strRand, 13 * i + 6, 16);//把该数字用画笔在画布画出,并指定数字的坐标 randomCode.append(strRand);//把该数字加到缓存字符串中。用于等会生成验证码字符串set到session中用于校对 } buffImg.flush();//清除缓冲的图片 g.dispose();//释放资源 session.setAttribute("randomCode", randomCode.toString());//把验证码set为属性 //设置页面不缓存 response.setContentType("image/jpeg"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); ServletOutputStream outputStream = response.getOutputStream(); ImageIO.write(buffImg, "jpeg", outputStream);//使用支持jpeg格式的 ImageWriter 将一个图像写入 OutputStream。而在客户端的img标签通过src来从中提取出jpeg图片 outputStream.close(); /* 这两句代码用于解决报错:java.lang.IllegalStateException: getOutputStream() has already been called 由于jsp container在处理完成请求后会调用releasePageContet方法释放所用的PageContext object, 并且同时调用getWriter方法,由于getWriter方法与在jsp页面中使用流相关的getOutputStream方法冲突, 所以会造成这种异常 */ out.clear(); out = pageContext.pushBody();
更进一步的验证:算术表达式验证。
在上面提到的验证码只不过是一些普通的数字(也可以用字母)图片,用户看着图片的内容输入图片里的数字、字母即可完成验证。其实,这还不算很安全,比如我们可以获取这张图片,通过模式识别等技术解析出图片的内容,那么这样的话图片就起不到拦截“非人类”的作用了。对此,我们可以在单纯的“识别”能力验证上加上一个“逻辑”能力验证——算术验证。
算术验证与普通图片验证不同的地方就在于,取代单纯的数字、字母图片,而采用算术表达式图片作为验证码。用户不仅需要辨析图片,还要在图片的基础上进行简单的算术运算,把运算结果作为验证码。
技术要点:把普通验证码中生成随机数字改为生成随机数字+随机运算符(加减乘除),画成图像发回浏览器显示。为在服务器端,则直接通过算术表达式算出验证码结果,set到session中作为属性以供其他负责验证的jsp文件中用作验证。
实例代码:
int width = 62, height = 22; BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = buffImg.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.setColor(Color.BLACK); g.drawRect(0, 0, width - 1, height - 1); g.setColor(Color.GRAY); Random random = new Random(); for (int i = 0; i < 40; i++) { int x1 = random.nextInt(width); int y1 = random.nextInt(height); int x2 = random.nextInt(10); int y2 = random.nextInt(10); g.drawLine(x1, y1, x1 + x2, y1 + y2); } Font font = new Font("Times New Roman", Font.PLAIN, 18); g.setFont(font); String[] ops={"+","-","*","/","="};//定义运算符 int num1=random.nextInt(10);//生成第一个操作数 String strRand1 = String.valueOf(num1); int red1 = random.nextInt(255); int green1 = random.nextInt(255); int blue1 = random.nextInt(255); g.setColor(new Color(red1, green1, blue1)); //画出第一个操作数 g.drawString(strRand1, 13 *0 + 6, 16); int op_num=random.nextInt(4);//随机生成一个运算符数组中的下标,从而得到随机的一个运算符。这里是0~3之间一个随机值。因为4是等号 String strRand2 =(String)ops[op_num]; int red2 = random.nextInt(255); int green2 = random.nextInt(255); int blue2 = random.nextInt(255); g.setColor(new Color(red2, green2, blue2)); //画出操作运算符 g.drawString(strRand2, 13 *1 + 6, 16); int num2=(random.nextInt(9)+1); //随机生成0~8之间的一个数+1,作为第二个操作数。因为有可能出现除法,所以第二个操作数不能为0。所以+1,使数在1~9之间。 String strRand3 = String.valueOf(num2); int red3 = random.nextInt(255); int green3 = random.nextInt(255); int blue3 = random.nextInt(255); g.setColor(new Color(red3, green3, blue3)); //画出第二个操作数 g.drawString(strRand3, 13 *2 + 6, 16); String strRand4 =(String)ops[4] ; int red4 = random.nextInt(255); int green4 = random.nextInt(255); int blue4 = random.nextInt(255); g.setColor(new Color(red4, green4, blue4)); //画出等号 g.drawString(strRand4, 13 *3 + 6, 16); Integer randomCode=0; //由运算符的不同执行不同的运算,得到验证码结果值 switch(op_num){ case 0: randomCode = num1+num2; break; case 1: randomCode = num1-num2; break; case 2: randomCode = num1*num2; break; case 3: randomCode = num1/num2; break; } session.setAttribute("randomCode", randomCode.toString());//把运算符结果值set到session中,用于其他文件进行验证码校对 buffImg.flush(); g.dispose(); response.setContentType("image/jpeg"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); ServletOutputStream outputStream = response.getOutputStream(); ImageIO.write(buffImg, "jpeg", outputStream); outputStream.close(); out.clear(); out = pageContext.pushBody();