网页验证码实现步骤及细节
业务部门提出要做一个客户自助查询的系统,而且这个系统是要公布到互联网上的。这样的话,除了一般常见的防注入、服务器安全外,最简单的就是在用户登录处加一个验证码,可以在一定程度上加大恶意尝试破解用户密码的难度。
在网上搜索了一天的类似内容,做成了一个自己的小程序,与大家共同分享,并将使用过程中的小细节说一下,一则备忘,二则也许能让后来的朋友多了解到一点东西吧。
效果图:
老样子,先上代码。个人的习惯,对于做为系统的纯输出功能的代码,使用ashx文件(一般处理程序)而不是aspx文件,呵呵。
先建立一个verify_code.ashx文件,代码如下:
<%@ WebHandler Language="VB" Class="verify_code" %>
Imports System
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.Drawing.Text
Imports System.Web
Imports System.Web.SessionState
Imports System.Web.Configuration
Public Class verify_code : Implements IHttpHandler, IRequiresSessionState
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
'定义图片大小
Dim bitMapImage As New Bitmap(80, 24)
Dim verity_code As String
'生成图片
DisturbBitmap(bitMapImage)
Dim graphicImage As Graphics = Graphics.FromImage(bitMapImage)
graphicImage.SmoothingMode = SmoothingMode.HighSpeed
'指定字体、大小、风格
Dim fontfamily As New FontFamily("Consolas")
Dim font As New Font(fontfamily, 20, FontStyle.Regular, GraphicsUnit.Pixel)
'生成六位随机字符,使用GetRandomint为生成一个六位随机数字
verity_code = GetRanChar(6)
'将随机字符写到图片中
graphicImage.DrawString(verity_code, font, Brushes.Green, New Point(1, 1))
'在外围画一个边框
graphicImage.DrawRectangle(New Pen(Color.Green, 0), 0, 0, bitMapImage.Width - 1, bitMapImage.Height - 1)
'输出格式为JPG文件
context.Response.ContentType = "image/jpeg"
'将生成的验证码写到Session中,供程序判断
context.Session("verify_code") = verity_code
'输出验证码图片
bitMapImage.Save(context.Response.OutputStream, ImageFormat.Jpeg)
graphicImage.Dispose()
bitMapImage.Dispose()
End Sub
Public Function GetRandomint() As String
Dim random As New Random()
Return (random.[Next](100000, 999999).ToString())
End Function
Public Function GetRanChar(Optional ByVal vinum As Integer = 6) As String
Dim Vchar As String
Dim Vnum As String = ""
If vinum = 0 Then
vinum = 6
End If
'字符串中没有使用0和O,以及小写的L,以免在验证码上看不清楚
Vchar = "2,A,B,C,D,1,E,F,G,H,I,4,J,K,L,M,N,6,P,Q,R,S,T,8,U,W,X,Y,7,Z,a,b,c,d,e,5,f,g,h,i,j,k,m,n,p,3,q,r,s,t,9,u,v,w,x,y,z"
Dim VcArray() As String = Vchar.Split(",")
Dim random As New Random()
Dim i As Integer
Dim iNum As Integer
For i = 1 To vinum
iNum = VcArray.Length
While iNum = VcArray.Length
iNum = Convert.ToInt32((VcArray.Length) * random.NextDouble())
End While
Vnum = Vnum + VcArray(iNum)
Next
Return Vnum
End Function
Private Sub DisturbBitmap(ByVal map As Bitmap)
Dim random As New Random()
'通过随机数生成
Dim k As Integer = 0
While k < 80
Dim j As Integer = 0
While j < 24
'在8%的随机位置产生噪点,100就是无噪点,一般不要小于85
If random.Next(0, 100) <= 92 Then
map.SetPixel(k, j, Color.AliceBlue)
End If
System.Math.Max(System.Threading.Interlocked.Increment(j), j - 1)
End While
System.Math.Max(System.Threading.Interlocked.Increment(k), k - 1)
End While
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
使用以下方式就可以在页面指定的地方显示验证码:
<img id="Verify_code" src="Verify_Code.ashx" width="80" height="24" />
代码应该都不复杂,大家可以看明白,如果不明白,复制过去也能使用,就不再多说了,因为这方面的资料也很多,下面就我在编写过程中发现的一些小细节与大家共同讨论一下:
一、验证码的刷新:
如果生成的验证码实在看不清楚,用户就需要刷新一个验证码,很多网站都提供了点击验证码后自动刷新,查看了一下代码,有些是使用了jquery的ajax功能,但我感觉有些复杂,后来用下面这种方法处理了,效果也极好。
<img id="Verify_code" src="Verify_Code.ashx?" alt="看不清?点击更换" onclick="this.src=this.src+'?'" width="80" height="24" />
二、验证码使用的字体:
如果使用默认的字体,网上的很多识别器都能很快的识别,因为我就想到了用一些不常见的字体来处理这个问题,但在使用中发现了以下的几个情况,大家以后也可用来参考:
1、并不是每一种在字体文件夹中的字体都可以使用,要asp.net能识别出来的才行,否则就只能使用默认字体;
2、新安装的字体有时不能正常使用,网上有资料说是重启一下IIS,但我是直接重启了服务器,有部分字体就被识别出来了;
3、建议使用等宽字体,如果是不等宽字体,万一生成的验证码都是W、M这些宽体字符的话,可能原来指定的图片放不下,影响效果,个人建议使用Consolas字体;
4、使用不同的字体时,字体大小会有很大差别,需要很多次尝试后使用最合适的字体大小,太窄的字体、笔划太细的字体都不适合做为验证码,加上噪点后很看识别出来,也可以使用手写体的数字(最好不要用字母了,不是外国人,真的不好认),不加噪点,也是一种方法。
下面为大家提供一个代码,可以枚举出系统中已安装且可以被asp.net识别的字体,这些字体就可以用在验证码的生成上了:
原文:http://msdn.microsoft.com/zh-cn/library/0yf5t4e8(v=VS.80).aspx
新建一个test.aspx文件,将代码放到test.aspx.vb中直接运行就可以了,前台代码不用修改:
Imports System.Drawing
Imports System.Drawing.Text
Partial Class test
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim fontFamily As New FontFamily("Arial")
Dim familyName As String
Dim familyList As String = ""
Dim fontFamilies() As FontFamily
Dim installedFontCollection As New InstalledFontCollection()
fontFamilies = installedFontCollection.Families
Dim count As Integer = fontFamilies.Length
Dim j As Integer
While j < count
familyName = fontFamilies(j).Name
familyList = familyList & familyName
familyList = familyList & ", "
j += 1
End While
Response.Write(familyList)
End Sub
End Class
大家可以根据自己页面的配色方案,为验证码图片设定不同的背景色、字体颜色、边框色等,我在尝试时发现,颜色那里只接受color数据类型,而不是常见的HTML颜色代码,我也没有再去研究怎么将颜色转换过去了,将就点用吧,但它的颜色都是用单词来设定了,我找到了一个指定颜色的网页,大家可以用来进行一个参考:
颜色代码对照表:http://samples.msdn.microsoft.com/workshop/samples/author/dhtml/colors/ColorTable.htm
测试时发现一个问题,在VS里F5时,没有错,在客户端进行测试时,会因为浏览器缓存的原因导致显示的图片与实际的不符,但这种情况不多且一般客户不会像测试时一样大量的刷新,我就没有再处理,有需要可以自己加上不缓存的代码。
这个验证码系统使用的是用Session来存储验证码的,因此就会有一个情况,如用户打开了二个及以上的窗口时,可能提交到后台时,后台就只会判断最后一次生成的验证码了,如果你的系统要求避免这种情况的话,可以先将生成的验证码进行加密,把加密字符串赋值给一个hidden控件,不过这样的话可能使用jquery控件来处理就更方便了,有空我也试一下,呵呵。
能想到的基本就是这么多吧,大家可以对代码自己进行修改,加上斜体、黑体、删除线、下划线等随机效果,加强验证码的强度。
没有使用更复杂的效果,是考虑到验证码只是一种手段,我们需要其他更多的后台手段来实现系统的安全,如果把精力都放在了验证码上,生怕被识别系统识别出来,一个验证码做的要用户刷新七八次才看认出一部分的话,那就应该本末倒置了,不是提供系统安全,而是折腾人了,这样的话,如果不是非的必要,是没有人愿意再来访问你的网站了。
主要参考资料:
http://www.cnblogs.com/thcjp/archive/2006/07/06/444342.html