轉載請注明原文地址: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" onclick="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();