網頁設計中,尤其表單填寫提交過程中,為防止機器自動登錄,很多網頁都采用驗證碼技術,允許用戶輸入而盡量避免自動登錄。驗證碼實現的方法有很多,PHP繪圖技術可以在服務端生成驗證碼并發送客戶端,HTML5技術下可以使用canvas與JS腳本實現在客戶端瀏覽器自動生成驗證碼。本文給出JS+Canvas驗證碼的解決措施,所制作驗證碼實現效果如下圖所示:
驗證碼實現效果動態圖
本例驗證碼的實現主要包括驗證碼字符串的生成、背景干擾點實現及干擾直線的生成三部分。最終通過canvas繪圖技術將生成的驗證碼字符串、背景及干擾直線顯示到畫布上。主要涉及技術或知識點包括canvas繪圖技術、數組、鼠標點擊事件、隨機函數等。以下從驗證字符串、背景干擾點及干擾直線三方面對實現過程進行說明。
驗證字符串部分主要借助數組存儲驗證碼所有字符,通過調用Math對象的隨機函數獲取數組下標,并通過數組下表讀取數組元素,將讀取的數組元素組裝成完整字符串。其實現核心代碼如下:
驗證字符串獲取核心代碼
如上圖所示,本例驗證碼字符包括數字與大寫字母,getCode函數返回值即為4位驗證碼字符串。
背景干擾點可以直接使用canvas對應的繪圖方法進行繪制,本例為簡化開發過程,降低難度直接使用drawImage繪圖方式加載背景圖片,實現干擾點效果。背景圖片如下:
背景干擾點圖片
通過調用drawImage方法,指定截取的坐標位置參數,可實現背景干擾點的動態變化效果,背景干擾點實現核心代碼如下:
背景干擾點實現代碼
其中getXsize與getYsize為獲取繪圖截取背景圖片的坐標位置,通過使用隨機函數實現從背景圖片不同位置截取進行繪圖輸出。
干擾直線實現較為簡單,直接通過JS提供的moveTo與lineTo方法完成直線的繪制,本例繪制了兩條直線,一條為黑色干擾線,一條為白色干擾線。在繪制過程兩端點需要使用Math隨機函數生成符合條件隨機坐標。干擾直線相關實現代碼如下:
干擾直線實現核心代碼
干擾直線實現核心代碼如上圖,其中getLsize方法主要用于獲取隨機端點Y軸坐標值。strokeStyle主要用于設置繪圖直線的顏色。
驗證碼的顯示輸出主要使用fillText()方法在canvas指定位置進行文字輸出,本例使用第三方ttf字體,因此在HTML頁面中對字體進行了加載。驗證碼顯示輸出實現代碼如下:
驗證碼繪制
驗證碼顯示輸出核心代碼如上圖所示,其中myfont為加載的第三方字體。
繪圖基礎部分主要包括前端canvas元素的布局等。包括屬性的設置,js部分元素的獲取及屬性設置等。
我們一般接觸的驗證碼,都可以點擊圖片實現驗證碼的刷新,因此本例為canvas標記添加了onclick事件,將所有驗證碼生成的代碼封裝到showCode()函數中,通過調用showCode函數實現驗證碼的刷新。本例完整JS腳本部分代碼如下:
JS實現腳本代碼
本頭條號長期關注編程資訊分享;編程課程、素材、代碼分享及編程培訓。如果您對以上方面有興趣或代碼錯誤、建議與意見,可以聯系作者,共同探討。期待大家關注!如需案例完整代碼請關注并私信,往期前端設計文章鏈接如下:
網頁頁面的使用中為防止“非人類”的大量操作和防止一些的信息冗余,增加驗證碼校驗是許多網站常用的方式。
而讓用戶輸入字母和數字組合的驗證碼是最經典也是最常用的方式。
這一篇是純利用現有JDK提供的繪圖類(ImageIO)類制作,這個過程比較復雜且需要了解ImageIO類。
今天發布的第二篇文章是利用Hutool工具類來實現的,該工具類已經封裝驗證碼所需的相關類等,使用起來較為簡單和方便。
驗證碼的生成和校驗過程均使用Servlet和JSP的結合來實現,Servlet的相關內容可以參閱
Servlet技術:https://mp.weixin.qq.com/s/__e_ef0SI6kVPiRaU0MXJw
如何利用基礎的JSP知識來實現網頁的驗證碼校驗呢?
首先要驗證碼的校驗的過程。
驗證碼校驗分為三部分:
驗證碼的生成實際就是輸出一個圖像,所以在這里使用ImageIO來生成圖片,然后結合使用隨機數(Random)來實現隨機生成驗證上的內容,最后進而展示出來,然后利用Session對象存儲驗證碼的內容。在用戶輸入驗證碼的時候可以用request來獲取用戶輸入的內容,讓其余Session對象中保存的驗證碼內容進行比較,若一致則驗證成功,不一致就驗證失敗。
先創建一個圖片的緩沖區:
BufferedImage bi=new BufferedImage(68, 22,BufferedImage.TYPE_INT_RGB);
創建畫布:
Graphics g=bi.getGraphics();
創建顏色:
Color c=new Color(200,150,255);
創建背景顏色:
g.setColor(c);
填充矩形:
g.fillRect(0, 0, 68,22);
將要顯示的驗證碼內容組成元素存入字符串數組:
char[] ch="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
創建隨機的驗證碼內容:
Random r=new Random();
int len=ch.length;
int index; //index用于存放隨機數字
StringBuffer sb=new StringBuffer();
for(int i=0;i<4;i++)
{
index=r.nextInt(len);//產生隨機數字
g.setColor(new Color(r.nextInt(88),r.nextInt(188),r.nextInt(255))); //設置顏色
g.drawString(ch[index]+"",(i*15)+3, 18);//畫數字以及數字的位置
sb.append(ch[index]);
}
將驗證碼的內容存入Session及顯示在頁面上:
request.getSession().setAttribute("piccode",sb.toString());
ImageIO.write(bi, "JPG", response.getOutputStream());
完整代碼:
public class ImageServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
BufferedImage bi = new BufferedImage(68, 22, BufferedImage.TYPE_INT_RGB);//創建圖像緩沖區
Graphics g = bi.getGraphics(); //通過緩沖區創建一個畫布
Color c = new Color(200, 150, 255); //創建顏色
g.setColor(c);//為畫布創建背景顏色
g.fillRect(0, 0, 68, 22); //填充矩形
char[] ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();//轉化為字符型的數組
Random r = new Random();
int len = ch.length;
int index; //index用于存放隨機數字
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
index = r.nextInt(len);//產生隨機數字
g.setColor(new Color(r.nextInt(88), r.nextInt(188), r.nextInt(255))); //設置顏色
g.drawString(ch[index] + "", (i * 15) + 3, 18);//畫數字以及數字的位置
sb.append(ch[index]);
}
request.getSession().setAttribute("piccode", sb.toString());
ImageIO.write(bi, "JPG", response.getOutputStream());
}
}
在測試之前需要先在web.xml文件中配置一下:
<servlet>
<servlet-name>ImageServlet</servlet-name>
<servlet-class>com.kailong.servlet.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/imageServlet</url-pattern>
</servlet-mapping>
啟動服務器后在瀏覽器中輸入http://localhost:8080/工程名/imageServlet 即可
驗證碼的生成已經實現成功,下面實現驗證驗證碼的Servlet。
先新建一個jsp用戶界面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄界面</title>
</head>
<body>
<form action="<%= request.getContextPath()%>/loginServlet" method="get" >
驗證碼:<input type="text" name="checkCode"/><br/>
<img alt="驗證碼" id="imagecode" src="<%= request.getContextPath()%>/imageServlet"/>
<input type="submit" value="提交">
</form>
</body>
</html>
校驗驗證碼過程:
代碼實現:
獲取Session中的驗證碼內容:
String piccode=(String) request.getSession().getAttribute("piccode");
獲取用戶輸入的驗證碼內容:
String checkCode=request.getParameter("checkCode");
驗證碼判斷(使用了PrintWriter將相關內容輸出)
response.setContentType("text/html;charset=utf-8");//解決亂碼問題
PrintWriter out=response.getWriter();
if(checkCode.equals(piccode))
{
out.println("驗證碼輸入正確!");
}
else
{
out.println("驗證碼輸入錯誤!!!");
}
out.flush();//將流刷新
out.close();//將流關閉
完整代碼:
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
//用于驗證驗證碼
{
String piccode = (String) request.getSession().getAttribute("piccode");
String checkCode = request.getParameter("checkCode");
response.setContentType("text/html;charset=utf-8");//解決亂碼問題
PrintWriter out = response.getWriter();
if (checkCode.equals(piccode)) {
out.println("驗證碼輸入正確!");
} else {
out.println("驗證碼輸入錯誤!!!");
}
out.flush();//將流刷新
out.close();//將流關閉
}
}
測試前先在web.xml文件中配置一下:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.kailong.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/loginServlet</url-pattern>
</servlet-mapping>
在驗證碼生成之后,用戶在識別的時候可能不能正確識別,這時候就需要刷新一下重新生成。
添加超鏈接實現刷新:
login.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄界面</title>
<script>
function reloadCode() {
var time=new Date().getTime();
document.getElementById("imagecode").src="<%= request.getContextPath()%>/imageGenerate?d="+time;
}
</script>
</head>
<body>
<form action="<%= request.getContextPath()%>/loginServlet" method="get" >
驗證碼:<input type="text" name="checkCode"/><br/>
<img alt="驗證碼" id="imagecode" src="<%= request.getContextPath()%>/imageServlet"/>
<a href="javascript:reloadCode();">看不清楚</a><br>
<br/><input type="submit" value="提交">
</form>
</body>
</html>
js部分的Date相關是防止瀏覽器緩存后不能正常刷新,添加時間的唯一性來實現能夠及時刷新和展示。
js 部分可以參閱:JavaScript 語言入門: https://mp.weixin.qq.com/s/37CaC25_1agb-aXBLhUKtg
也可以在ImageServlet中添加防止瀏覽器緩存的語句:
response.setHeader("Pragma", "No-cache");
公眾號本文地址:https://mp.weixin.qq.com/s/XHucabQ_WwUx2OMDGSTMkw
歡迎關注公眾號:愚生淺末。
近有小伙伴提問:能否說下web驗證的原理,感覺文字描述不清楚,于是就用代碼簡單的演示下:此代碼是需要依賴:
sanic==19.9.0Pillow==7.0.0
import random
import string
import uuid
import base64
import platform
from PIL import Image, ImageDraw,ImageFont
from io import BytesIO
from sanic import Sanic
from sanic.response import HTTPResponse,text
from sanic.views import HTTPMethodView
app = Sanic()
session = {}
class VerifyCode:
def __init__(self, numbers:int):
"""
指定:生成的數量
"""
self.number = numbers
def draw_lines(self, draw, num, width, height):
"""劃線"""
x1 = random.randint(0, width / 2)
y1 = random.randint(0, height / 2)
x2 = random.randint(0, width)
y2 = random.randint(height / 2, height)
draw.line(((x1, y1), (x2, y2)), fill='black', width=1)
def random_color(self):
"""隨機顏色"""
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
def gene_text(self):
"""生成驗證碼"""
return "".join(random.sample(string.ascii_letters+string.digits, self.number))
def get_verify_code(self):
"""
draw.text():
文字的繪制,第一個參數指定繪制的起始點(文本的左上角所在位置),第二個參數指定文本內容,第三個參數指定文本的顏色,第四個參數指定字體(通過ImageFont類來定義)
"""
code = self.gene_text()
width, height = 130, 30
im = Image.new("RGB", (width, height), "white")
# 這里指定字體的路徑
sysstr = platform.system()
font = None
if sysstr == "Windows":
font = ImageFont.truetype("C:\WINDOWS\Fonts\STXINGKA.TTF", 25)
elif sysstr == "Darwin":
font = ImageFont.truetype('/Library/Fonts/AppleMyungjo.ttf', 25)
draw = ImageDraw.Draw(im)
for item in range(self.number):
draw.text((5+random.randint(-5,5)+23*item, 5+random.randint(-5, 5)), text=code[item],
fill=self.random_color(), font=font)
self.draw_lines(draw, self.number, width, height)
return im, code
class SimpleView(HTTPMethodView):
body = """
<html>
<head>
<meta charset="UTF-8">
<title>登錄</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可選的 Bootstrap 主題文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<form class="form-inline" method="post" action="/code">
<div>
<div class="form-group">
{error}
</div>
<div class="form-group">
<label for="exampleInputName2">驗證碼</label>
<input type="text" class="form-control" id="captcha" name="code">
</div>
<div class="form-group">
<img src="data:image/jpeg;base64,{base64_data}" class="img-img-rounded">
</div>
<div>
<button type="submit">驗證</button>
</div>
</div>
</form>
</body>
</html>
"""
async def get(self, request):
return self.response(error="")
async def post(self, request):
uuid = request.cookies.get("uuid", "1")
verfy_code = request.form.get("code", "2").lower()
code = session.get(uuid, "").lower()
if code == verfy_code:
return text('驗證碼正確')
return self.response(error='<input class="form-control" id="disabledInput" type="text" placeholder="驗證碼錯誤" disabled>')
def response(self, error):
im, code = VerifyCode(5).get_verify_code()
buf = BytesIO()
im.save(buf, "jpeg")
buf_str = buf.getvalue()
base64_data = base64.b64encode(buf_str).decode()
id = uuid.uuid1().__str__()
session[id] = code
body = self.body.format(base64_data=base64_data, error=error)
response = HTTPResponse(body, content_type="text/html; charset=utf-8")
response.cookies["uuid"] = id
return response
app.add_route(SimpleView.as_view(), '/code')
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
只是簡單的實現了驗證碼,沒有實現點擊刷新,點擊刷新的原理不難:異步請求+刷新接口就好了,記得更新對應的session的key里面的value
0人點贊
隨筆
*請認真填寫需求信息,我們會在24小時內與您取得聯系。