日期:2011-07-10  浏览次数:20994 次

我们上次讨论过,使用ASP.NET里表单验证最简单的方法是把所有的信息都保存在Web.config文件里,包括用户名和密码。这是让基本的验证在你网站上迅速起作用和运行的好方法,但是它还是有很大的局限性。尤其是:

·你无法使用注册表单让新用户添加他们自己,因为在网站运行的时候,你不能编辑应用程序的代码,也不能重新保存Web.config文件。
·通过手动编辑Web.config文件来添加用户是很辛苦的,它还需要你的Web应用程序重启,这会影响到已有的访问者。
·用户不能定期更改自己的密码。

要突破这些局限,我们就应该停止使用Web.config作为保存用户信息的地方,并把这些信息转移到数据库里。在用户填好登录表单之后,我们就可以通过修改验证代码来查询这个数据库了。下面就是我们要做的,简而言之就是:

1、更改Web.config,把直接保存在里面的用户信任书移走。
2、更改我们登录按钮的代码,这样它就能够调用另一个函数,从而将用户信息和数据库里的信息进行对比验证。
3、编写一个validateUser方法,负责辨别用户是否存在以及他或者她是否提供了正确密码的大部分工作都由它完成。
4、编写一个addUser方法,用来把新用户放到数据库里。

为了保持连贯性,我们基本上会使用前一篇文章里用过的代码列表,只是要在所需要的地方进行修改。但是这不意味着你就一定需要读前面的文章,你可以就从本文开始。

Web.config

Web.config是一个配置文件,所有ASP.NET的应用程序一般都会把这个文件放在其根目录下,它包含有<authentication>和<authorization>这两个区段。<authentication>区段里包含了ASP.NET如何辨别访问者谁是谁的细节,而<authorization>区段会指出哪个通过验证的用户能够访问目录下的.aspx文件。

Listing A里的Web.config文件和我们上次用到的是一样的,只有一个很大的不同之处(我们马上就会讲到这一点)。<authentication>元素会指出ASP.NET应该使用验证的Forms方法,这和所集成的Windows验证以及微软的Passport验证是相对的。<forms>元素会接着给出更多的细节,用来确定ASP.NET应该使用表单认证,并指出登录页面放在哪里。在前一篇文章里,我们还使用了<credentials>元素,所以我们能够把用户名和密码就保存在Web.config文件里。这一次,我们把<credentials>区段留在那里作为参考,但是把它包括在XML注释标签里,所以它就不会被ASP.NET使用。这就是那个很大的不同。

最后,带有<deny users="?"/>子元素的<authorization>元素会指出,匿名用户不被允许看到.aspx页面。每个人都会被强制进行验证,这就意味着在每次会话中,它们至少会被送到登录页面一次。
 
登录按钮

Listing B包含了一个用于登录表单按钮的点击事件句柄。再强调一次,我们在上一次就把所有的代码都放在这个列表里了,只不过我们上次在不需要的代码前面标上了注释符,并清楚地表示过我们这次要加入哪些代码。

如列表所示,我们不再是简单地让点击事件调用FormsAuthentication类的Authenticate方法。这个方法会搜索Web.config文件里的用户名和密码,并把它们同传递来的参数进行对比。但是由于我们使用了数据库来保存用户名和密码,所以我们会编写自己的方法——validateUser,我们下面就会讲到它。这个按钮的点击事件会调用我们的validateUser方法,并把用户在Web表单文本字段里提供的用户名和密码传递给它。如果验证成功,那么RedirectFromLoginPage就会被调用,这样用户就会被重新引导到他或者她在被提示登录之前想要访问的页面上。

ValidateUser方法

Listing C包含了validateUser方法,它具有下面这些功能:

·它能够接受用户名和密码作为字符串参数。
·它使用.NET所提供的简单方法对密码进行散列(hash)操作,因为数据库里的密码也经过了散列
  处理,这样密码就不会以明文出现。
·它能够连接到数据库,并运行一个查询,用来在含有用户名和密码的表格里寻找用户名。
·如果它找到了用户名,它就会将保存在数据库里的密码同作为方法参数的经过散列的密码进行比
  较。

列表里的数据库代码是相当简单的。因为这不是一篇关于.NET数据访问的文章,所以我们不用深入细节。但是,在数据库代码之前对密码的散列操作是需要特别注意地。

如果你正准备把用户名和密码保存到数据库里,那就要考虑变换密码,这样它就不会以明文出现数据库的字段里,明文很容易被盗。.NET提供了一个很简单的方法把密码变换成一串毫无疑义的字符串。这个方法是HashPasswordForStoringInConfigFile,它也是FormsAuthentication类的一部分。不要让这个方法长名字的“ConfigFile”部分给吓住;这个方法只会对传递给它的文本进行变换,然后你就可以把结果放在你想放的任何地方。

下面是散列方法能干什么的一个例子。如果你提供这段C#代码:

string hashed =
FormsAuthentication.HashPasswordForStoringInConfigFile(
“password”,”SHA1”);

那么经过散列变换就会变成下面的字符串:
5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8

对密码进行散列操作的不足之处是,(就我的知识能力而言)没有办法帮助忘记密码的用户找回密码。换句话说,你不能反散列它找回明文。但是,你可以为这样的用户分配一个新的临时密码,然后他或者她就可以先登录再更改密码,只要你的网站支持这个功能的话。

添加用户

我们现在可以讨论用来向你的网站添加用户的代码了。由于直接在Web.config里保存用户名和密码的限制,我们不能在上一篇文章里讨论这个问题。既然已经转到使用数据库上了,那我们可以编写一个简单的addUser方法来接受用户名和密码,并散列密码,再把新的行插入到数据库表格里。Listing D是这样一个方法的例子。

正如你所看到的,这段代码和validateUser方法的代码很类似:用户名和明文密码被传递进来,密码被散列,这个被散列的密码会被用在数据库代码里。它们之间巨大的不同是:数据库代码用到了SQL的INSERT而不是SELECT,这就意味着它不需要DataReader类对象。

我们通过提交注册表单里的点击事件来调用addUser方法,例如图A里的简单表单。

改进的余地

我们的列表只是抛砖引玉,你应该考虑一些可能想要增加的东西。例如,如果validateUser失败了,那么登录按钮的点击事件句柄应该做些什么呢?我们的简单例子向页面上的控件添加了一条失败信息。但是如果用户一遍又一遍的尝试和失败,那又该怎么办呢?这个人是一个尝试猜测密码的未授权用户吗?你应该追踪validateUser的这些失败并在尝试失败几次后就锁定这个帐号吗?

而且要考虑一下,如果addUser所要试图添加的用户名在数据库里已经存在,那么这个方法应该怎么办。我们的示例代码不会考虑这种可能性,但是你用于实际工作的代码就要考虑了。

最后,虽然我们在本文里介绍了安全上的改进,但是要理解所存在的危险是很重要的。我们已经对密码进行了散列操作,并将它们保存在数据库而不是明文的配置文件里,这是走向安全的第一步。但是要记住的是,我们所有的散列操作都发生在ASP.NET的服务器代码里,而密码是从用户的浏览器穿过电缆到达服务器的,这一点非常重要。除非浏览器到你服务器的连接是通过安全套接字(SSL)进行的,否则通过电缆传送的密码就会是明文。如果用户密码的安全是首要的,那么你就应该要求到你网站的都是SSL连接。

事倍功半

和前一篇文章里基于Web.config的解决方案相比,通过把用户信息保存在数据库里,我们又向自己网站的认证系统里添加了相当多的功能。我们新的方法肯定需要更多的代码,但是这都不是很难的代码。在保存密码之前对密码进行散列操作的额外安全步骤只需要调用一个方法,所添加的其他代码大都是用于对数据库的标准访问的——这一