最近滑動驗證碼在很多網(wǎng)站逐步流行起來,一方面對用戶體驗來說,比較新穎,操作簡單,另一方面相對圖形驗證碼來說,安全性并沒有很大的降低。當(dāng)然到目前為止,沒有絕對的安全驗證,只是不斷增加攻擊者的繞過成本。
接下來分析下滑動驗證碼的核心流程:
后端隨機生成摳圖和帶有摳圖陰影的背景圖片,后臺保存隨機摳圖位置坐標(biāo)
前端實現(xiàn)滑動交互,將摳圖拼在摳圖陰影之上,獲取到用戶滑動距離值,比如以下示例
前端將用戶滑動距離值傳入后端,后端校驗誤差是否在容許范圍內(nèi)。
這里單純校驗用戶滑動距離是最基本的校驗,出于更高的安全考慮,可能還會考慮用戶滑動的整個軌跡,用戶在當(dāng)前頁面的訪問行為等。這些可以很復(fù)雜,甚至借助到用戶行為數(shù)據(jù)分析模型,最終的目標(biāo)都是增加非法的模擬和繞過的難度。這些有機會可以再歸納總結(jié)常用到的方法,本文重點集中在如何基于java來一步步實現(xiàn)滑動驗證碼的生成。
可以看到,滑動圖形驗證碼,重要有兩個圖片組成,摳塊和帶有摳塊陰影的原圖,這里面有兩個重要特性保證被暴力破解的難度:摳塊的形狀隨機和摳塊所在原圖的位置隨機。這樣就可以在有限的圖集中制造出隨機的、無規(guī)律可尋的摳圖和原圖的配對。
用代碼如何從一張大圖中摳出一個有特定隨機形狀的小圖呢?
第一步,先確定一個摳出圖的輪廓,方便后續(xù)真正開始執(zhí)行圖片處理操作
圖片是有像素組成,每個像素點對應(yīng)一種顏色,顏色可以用rgb形式表示,外加一個透明度,把一張圖理解成一個平面圖形,左上角為原點,向右x軸,向下y軸,一個坐標(biāo)值對應(yīng)該位置像素點的顏色,這樣就可以把一張圖轉(zhuǎn)換成一個二維數(shù)組?;谶@個考慮,輪廓也用二維數(shù)組來表示,輪廓內(nèi)元素值為1,輪廓外元素值對應(yīng)0。
這時候就要想這個輪廓形狀怎么生成了。有坐標(biāo)系、有矩形、有圓形,沒錯,用到數(shù)學(xué)的圖形函數(shù)。典型用到一個圓的函數(shù)方程和矩形的邊線的函數(shù),類似:
(x-a)²+(y-b)²=r²中,有三個參數(shù)a、b、r,即圓心坐標(biāo)為(a,b),半徑r。這些將摳圖放在上文描述的坐標(biāo)系上很容易就圖算出來具體的值。
示例代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private int [][] getblockdata() { int [][] data = new int [targetlength][targetwidth]; double x2 = targetlength-circler- 2 ; //隨機生成圓的位置 double h1 = circler + math.random() * (targetwidth- 3 *circler-r1); double po = circler*circler; double xbegin = targetlength-circler-r1; double ybegin = targetwidth-circler-r1; for ( int i = 0 ; i < targetlength; i++) { for ( int j = 0 ; j < targetwidth; j++) { //右邊○ double d3 = math.pow(i - x2, 2 ) + math.pow(j - h1, 2 ); if (d1 <= po || (j >= ybegin && d2 >= po) || (i >= xbegin && d3 >= po) ) { data[i][j] = 0 ; } else { data[i][j] = 1 ; } } } return data; } |
第二步,有這個輪廓后就可以依據(jù)這個二維數(shù)組的值來判定摳圖并在原圖上摳圖位置處加陰影。
操作如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private void cutbytemplate(bufferedimage oriimage,bufferedimage targetimage, int [][] templateimage, int x, int y){ for ( int i = 0 ; i < targetlength; i++) { for ( int j = 0 ; j < targetwidth; j++) { int rgb = templateimage[i][j]; // 原圖中對應(yīng)位置變色處理 int rgb_ori = oriimage.getrgb(x + i, y + j); if (rgb == 1 ) { //摳圖上復(fù)制對應(yīng)顏色值 targetimage.setrgb(i, y + j, rgb_ori); int r = ( 0xff & rgb_ori); int g = ( 0xff & (rgb_ori >> 8 )); int b = ( 0xff & (rgb_ori >> 16 ))); rgb_ori = r + (g << 8 ) + (b << 16 ) + ( 200 << 24 ); //原圖對應(yīng)位置顏色變化 oriimage.setrgb(x + i, y + j, rgb_ori); } } } } |
經(jīng)過前面兩步后,就得到了摳圖和帶摳圖陰影的原圖。為增加混淆和提高網(wǎng)絡(luò)加載效果,還需要對圖片做進一步處理。一般有兩件事需要做,一對圖片做模糊處理增加機器識別難度,二做適當(dāng)同質(zhì)量壓縮。模糊處理很容易想到高斯模糊,原理很好理解,大家可以去google了解下。具體到j(luò)ava里面的實現(xiàn),有很多版本,現(xiàn)在不借助任何第三方j(luò)ar,提供一個示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public static convolveop getgaussianblurfilter( int radius, boolean horizontal) { if (radius < 1 ) { throw new illegalargumentexception( "radius must be >= 1" ); } int size = radius * 2 + 1 ; float [] data = new float [size]; float sigma = radius / 3 .0f; float twosigmasquare = 2 .0f * sigma * sigma; float sigmaroot = ( float ) math.sqrt(twosigmasquare * math.pi); float total = 0 .0f; for ( int i = -radius; i <= radius; i++) { float distance = i * i; int index = i + radius; data[index] = ( float ) math.exp(-distance / twosigmasquare) / sigmaroot; total += data[index]; } for ( int i = 0 ; i < data.length; i++) { data[i] /= total; } kernel kernel = null ; if (horizontal) { kernel = new kernel(size, 1 , data); } else { kernel = new kernel( 1 , size, data); } return new convolveop(kernel, convolveop.edge_no_op, null ); } public static void simpleblur(bufferedimage src,bufferedimage dest) { bufferedimageop op = getgaussianblurfilter( 2 , false ); op.filter(src, dest); } |
經(jīng)測試模糊效果很不錯。另外是圖片壓縮,也不借助任何第三方工具,做同質(zhì)壓縮。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public static byte [] frombufferedimage2(bufferedimage img,string imagtype) throws ioexception { bos.reset(); // 得到指定format圖片的writer iterator<imagewriter> iter = imageio.getimagewritersbyformatname(imagtype); imagewriter writer = (imagewriter) iter.next(); // 得到指定writer的輸出參數(shù)設(shè)置(imagewriteparam ) imagewriteparam iwp = writer.getdefaultwriteparam(); iwp.setcompressionmode(imagewriteparam.mode_explicit); // 設(shè)置可否壓縮 iwp.setcompressionquality(1f); // 設(shè)置壓縮質(zhì)量參數(shù) iwp.setprogressivemode(imagewriteparam.mode_disabled); colormodel colormodel = colormodel.getrgbdefault(); // 指定壓縮時使用的色彩模式 iwp.setdestinationtype( new javax.imageio.imagetypespecifier(colormodel, colormodel.createcompatiblesamplemodel( 16 , 16 ))); writer.setoutput(imageio .createimageoutputstream(bos)); iioimage iiamge = new iioimage(img, null , null ); writer.write( null , iiamge, iwp); byte [] d = bos.tobytearray(); return d; } |
至此,滑動驗證碼核心的代碼處理流程已全部結(jié)束,這里面有很多細(xì)節(jié)可以不斷打磨優(yōu)化,讓滑動體驗可以變得更好。希望可以幫助到某些準(zhǔn)備自己構(gòu)建滑動驗證碼的同學(xué)。
以上代碼實現(xiàn)都非常的精煉,一方面為了保證性能,另一方面好理解。另外由于各方面原因也不便引入過多細(xì)節(jié),如果疑問,可留言交流。經(jīng)測試,該生成滑動圖形的流程響應(yīng)時間可以控制在20ms左右,如果原圖分辨率在300px*150px以下,可以到10ms左右,在可接受范圍內(nèi)。如果有更高效的方式,希望多多指教。也希望大家多多支持服務(wù)器之家。
原文鏈接:https://my.oschina.net/yaohonv/blog/1604185