

















第二篇:Asp.Net事务和异常处理
学习MSDN的WEBCAST,感觉单纯看一遍意义不大,做个笔记,以备不时只需查找方便。代码部分本人在XP+VS2005+SQL2005测试通过,请大家指教。
Asp.net安全性,分为七个部分的议程。如下。
• 输入安全性
• 身份验证
• 授权
• ASP.NET模拟
• 存储机密
• 使用加密
• ASP.NET安全使用最佳实践
首先是输入安全性,讲师讲解了SQL Injection的工作原理,比如说应用中的模型是Query模型是
SELECT COUNT (*) FROM Users WHERE UserName=‘Jeff’AND Password=‘imbatman’。
而恶意者在username对话框填写"'or 1=1--" 这样就达到了一个恶意的Query的目的,语句变成这样:
SELECT COUNT (*) FROM UsersWHERE UserName=‘’ or 1=1-- 从而使password验证实效,也就是可以把password验证给注释掉,从而达到恶意的目的。而且还可以通过SQL注入达到更可怕的目的,举个例子吧,对于这样的查询语句:string strSql = "select UserName,UserPass from tbUserInfo where UserName='"+tbName.Text+"' and UserPass='"+tbPass.Text+"'";可以这样的语句:';insert into tbUserInfo values('张三','111','男',")--来达到写入数据表的目的。
对于这样呢,我们可以通过验证输入来达到防范的目的,1,验证所有的输入:一般情况下使用asp.net的验证控件,其他情况下使用正则表达式(e.g.,web server 参数)。
2,对于输出的数据要加密。3,使用参数化的存储过程和查询语句。
我们做一个例子来对比下有参数的和无参数的查询语句。首先做页面:放二个TEXTBOX,一个输入用户名,一个输入密码,二个BUTTON一个是普通登录,一个是安全登录。代码如下:
<TABLE id="Table1" style="Z-INDEX: 102; LEFT: 182px; POSITION: absolute; TOP: 121px" cellSpacing="1" cellPadding="1" width="300" border="1">
<TR>
<TD>
<asp:Label id="Label1" runat="server">用户名称:</asp:Label></TD>
<TD>
<asp:TextBox id="tbName" runat="server" Width="183px"></asp:TextBox></TD>
</TR>
<TR>
<TD>
<asp:Label id="Label2" runat="server">密码:</asp:Label></TD>
<TD>
<asp:TextBox id="tbPass" runat="server" Width="183px"></asp:TextBox></TD>
</TR>
</TABLE>
<asp:Button id="btnLoginBetter" style="Z-INDEX: 105; LEFT: 343px; POSITION: absolute; TOP: 226px" runat="server" Width="78px" Text="安全登录" onclick="btnLoginBetter_Click"></asp:Button>
<asp:Label id="lbDiag" style="Z-INDEX: 104; LEFT: 189px; POSITION: absolute; TOP: 343px" runat="server" Width="599px"></asp:Label>
<asp:Button id="btnLogin" style="Z-INDEX: 100; LEFT: 213px; POSITION: absolute; TOP: 224px" runat="server" Text="登录" Width="78px" onclick="btnLogin_Click"></asp:Button>
<asp:Label id="lbMsg" style="Z-INDEX: 103; LEFT: 185px; POSITION: absolute; TOP: 282px" runat="server" Width="449px"></asp:Label>
在WEB.CONFIG里面添加连接字符串:
<connectionStrings>
<add name="DSN" connectionString="server=.;database=myDatabase;uid=sa;pwd="/>
</connectionStrings>
然后自己写验证语句,代码如下
protected void btnLogin_Click(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection();
con.ConnectionString =ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
con.Open();
//查询语句不带参数。
string strSql = "select UserName,UserPass from tbUserInfo where UserName='" + tbName.Text + "' and UserPass='" + tbPass.Text + "'";
SqlCommand sqlCom = new SqlCommand(strSql,con);
SqlDataReader dr = sqlCom.ExecuteReader();
lbDiag.Text = strSql;
//以下执行查询
bool bExist = false;
while(dr.Read())
{
bExist = true;
}
if(bExist)
lbMsg.Text = "您好!"+tbName.Text;
else
lbMsg.Text = tbName.Text + "不能进入!";
con.Close();


}

protected void btnLoginBetter_Click(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection();
con.ConnectionString = ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
con.Open();
//查询语句带参数。
string strSql = "select UserName,UserPass from tbUserInfo where UserName=@username and UserPass=@userpass";
SqlParameter sqlpUser = new SqlParameter("@username",SqlDbType.NVarChar,30);
sqlpUser.Value = tbName.Text;
SqlParameter sqlpPass = new SqlParameter("@userpass",SqlDbType.NVarChar,30);
sqlpPass.Value = tbPass.Text;
SqlCommand com = new SqlCommand(strSql,con);
com.Parameters.Add(sqlpUser);
com.Parameters.Add(sqlpPass);
SqlDataReader dr = com.ExecuteReader();
//以下执行查询
bool bExist = false;
while(dr.Read())
{
bExist = true;
}
lbDiag.Text = strSql;
if(bExist)
lbMsg.Text = "您好!"+Server.HtmlEncode(tbName.Text);
else
lbMsg.Text = Server.HtmlEncode(tbName.Text) + "不能进入!";
con.Close();
}
然后是身份验证:
身份验证是指以下过程:获取标识凭据(如用户名和密码),并对照某一颁发机构来验证这些凭据。
• ASP.NET 提供了四个身份验证提供程序:
– 表单身份验证
– Windows 身份验证
– Passport 身份验证
– 默认身份验证
请求的安全性事件流是这样的:
1,客户端请求aspx 页面。
2,将客户端凭据传递给 IIS。
3,IIS 对客户端进行身份验证,然后将经过身份验证的标记随客户端请求一起传送到 ASP.NET 工作进程。
4,根据 IIS 传送的经过身份验证的标记以及 Web 应用程序的配置设置,ASP.NET 决定是否在处理请求的线程上模拟用户。
下边对四种身份验证逐一说明。
1,
表单身份验证的开发步骤。
1. 将IIS 配置为使用匿名访问。
2. 将ASP.NET 配置为使用表单身份验证。
3. 创建登录Web 表单并验证提供的凭据。
4. 从自定义数据存储中检索角色列表。
5. 创建表单身份验证票(在票中存储角色)。
6. 创建一个IPrincipal 对象。
7. 将IPrincipal 对象放到当前的HTTP 上下文中。
8. 基于用户名/角色成员身份对用户进行授权。授权是指验证经身份验证的用户是否可以访问请求资源的过程。
• ASP.NET 提供以下授权提供程序:
– FileAuthorization:FileAuthorizationModule 类进行文件授权,而且在使用Windows 身份验证时处于活动状态。
– UrlAuthorization:UrlAuthorizationModule 类进行统一资源定位器(URL) 授权,它基于URI 命名空间来控制授权。URI 命名空间可能与NTFS 权限使用的物理文件夹和文件路径存在很大的差异。
• 若要建立访问特定目录的条件,则必须将一个包含<authorization> 部分的配置文件放置在该目录中。为该目录设置的条件也会应用到其子目录,除非子目录中的配置文件重写这些条件。此部分的常规语法如下所示。
<[element] [users] [roles] [verbs]/> 元素是必需的。必须包含users 或roles 属性。可以同时包含二者,但这不是必需的。
• 以下示例向Kim 和管理角色的成员授予权限,而拒绝John 和所有匿名用户:
<authorization>
<allow users="Kim"/>
<allow roles="Admins"/>
<deny users="John"/>
<deny users="?"/>
</authorization>若要允许John 并拒绝其他任何人,可以构造下面的配置部分。
<authorization> <allow users="John"/> <deny
users="*"/> </authorization>
下面的示例允许每个人使用GET,但只有Kim 可以使用POST。
<authorization>
<allow verb="GET" users="*"/>
<allow verb="POST" users="Kim"/>
<deny verb="POST" users="*"/>
</authorization>
关于表单验证也有一个例子,首先我们再新建一个表,不用上个例子那个表了,为了因为这次用存储过程来做,因为要使用加密字符串来操作。
use myDatabase
go
create table formsUi
(
username varchar(64) primary key,
passwordHash varchar(50) not null,
passwordSalt varchar(50) not null
)
go
然后写存储过程:
create procedure sp_getuserdetails
@acctname varchar(64),
@passhash varchar(50) out,
@passsalt varchar(50) out
as
select @passhash = passwordHash, @passsalt = passwordSalt from formsUi where userName = @acctname
go
数据库建立好了以后添加三个网页,首先在web.config里添加这样的字段:意思是起用表单验证。
<authentication mode="Forms">
<forms name=".SecurityDemo" loginUrl="login2.aspx" >
</forms>
</authentication>
一个是注册页面,注册时候就对密码进行加密:
<form id="Reg" method="post" runat="server">
<TABLE id="Table1" style="Z-INDEX: 101; LEFT: 167px; WIDTH: 446px; POSITION: absolute; TOP: 159px; HEIGHT: 72px" cellSpacing="1" cellPadding="1" width="446" border="1">
<TR>
<TD>
<asp:Label id="Label1" runat="server">用户名称:</asp:Label></TD>
<TD>
<asp:TextBox id="tbName" runat="server" Width="183px"></asp:TextBox></TD>
<TD>
<asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" ControlToValidate="tbName" ErrorMessage="用户名不能为空!"></asp:RequiredFieldValidator></TD>
</TR>
<TR>
<TD>
<asp:Label id="Label2" runat="server">密码:</asp:Label></TD>
<TD>
<asp:TextBox id="tbPass" runat="server" Width="183px"></asp:TextBox></TD>
<TD>
<asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ControlToValidate="tbPass" ErrorMessage="密码不能为空!"></asp:RequiredFieldValidator></TD>
</TR>
</TABLE>
<asp:Label id="Label3" style="Z-INDEX: 102; LEFT: 228px; POSITION: absolute; TOP: 52px" runat="server" Font-Size="XX-Large">用户注册页面</asp:Label>
<asp:Button id="btnReg" style="Z-INDEX: 103; LEFT: 322px; POSITION: absolute; TOP: 271px" runat="server" Width="77px" Text="注册" onclick="btnReg_Click"></asp:Button>
</form>
然后后台编码:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Configuration;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Security;
using System.IO;
using System.Text;

namespace WebApplication1
{
public partial class reg : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void btnReg_Click(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection();
con.ConnectionString=ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
con.Open();

//以下得到hash和salt加密串
const int salSize = 16;
// step 1: create some entropy for use as the salt
RandomNumberGenerator rng = RandomNumberGenerator.Create();
byte[] salt = new byte[salSize];
rng.GetBytes(salt);


// step 2: turn the password into bytes
byte[] secret = Encoding.Unicode.GetBytes(tbPass.Text);


// step 3: create the hash
HashAlgorithm hashAlg = SHA1.Create();
using (CryptoStream cs = new CryptoStream(Stream.Null, hashAlg, CryptoStreamMode.Write))
{
cs.Write(secret, 0, secret.Length);
cs.Write(salt, 0, salt.Length);
cs.FlushFinalBlock();
}
string strHash = Convert.ToBase64String(hashAlg.Hash);
string strSalt = Convert.ToBase64String(salt);
//
string strSql = "insert into formsUi values(@username,@hashPass,@saltPass)";
SqlParameter sqlpUser = new SqlParameter("@username", SqlDbType.NVarChar, 64);
sqlpUser.Value = tbName.Text;
SqlParameter sqlpPassHash = new SqlParameter("@hashPass", SqlDbType.NVarChar, 50);
SqlParameter sqlpPassSalt = new SqlParameter("@saltPass", SqlDbType.NVarChar, 50);
sqlpPassHash.Value = strHash;
sqlpPassSalt.Value = strSalt;
SqlCommand com = new SqlCommand(strSql, con);
com.Parameters.Add(sqlpUser);
com.Parameters.Add(sqlpPassHash);
com.Parameters.Add(sqlpPassSalt);
com.ExecuteNonQuery();
con.Close();
Response.Write("<script language='javascript'>alert('注册成功!')</script>");
}
}
一个网页做事例用也就是login2.aspx。我们这样简单设计:
<form id="login" method="post" runat="server">
<TABLE id="Table1" style="Z-INDEX: 102; LEFT: 182px; WIDTH: 446px; POSITION: absolute; TOP: 116px; HEIGHT: 72px" cellSpacing="1" cellPadding="1" width="446" border="1">
<TR>
<TD><asp:label id="Label1" runat="server">用户名称:</asp:label></TD>
<TD><asp:textbox id="tbName" runat="server" Width="183px"></asp:textbox></TD>
<TD><asp:requiredfieldvalidator id="RequiredFieldValidator1" runat="server" ErrorMessage="用户名不能为空!" ControlToValidate="tbName"></asp:requiredfieldvalidator></TD>
</TR>
<TR>
<TD><asp:label id="Label2" runat="server">密码:</asp:label></TD>
<TD><asp:textbox id="tbPass" runat="server" Width="183px"></asp:textbox></TD>
<TD><asp:requiredfieldvalidator id="RequiredFieldValidator2" runat="server" ErrorMessage="密码不能为空!" ControlToValidate="tbPass"></asp:requiredfieldvalidator></TD>
</TR>
<TR>
<TD><FONT face="宋体">是否保存Cookie</FONT></TD>
<TD><asp:checkbox id="PersistCookie" runat="server"></asp:checkbox></TD>
<TD></TD>
</TR>
</TABLE>
<asp:button id="btnLoginBetter" style="Z-INDEX: 105; LEFT: 312px; POSITION: absolute; TOP: 265px" runat="server" Width="78px" Text="登录" onclick="btnLoginBetter_Click"></asp:button></form>
然后编码,这里MSDN给的源码好像有问题,就是随便输入用户名和密码都能通过,问题出在数据流链接转换到加密输出的流那部分。我做了下修改:login2.aspx.cs:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Drawing;
using System.Web.SessionState;

namespace WebApplication1
{
public partial class login2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void btnLoginBetter_Click(object sender, EventArgs e)
{
bool bExist =AuthenticateUser(tbName.Text,tbPass.Text);
if(bExist)
{
//1) //创建一个验证票据
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, tbName.Text,DateTime.Now,
DateTime.Now.AddMinutes(30),PersistCookie.Checked,"user");
//2) //并且加密票据
string cookieStr = FormsAuthentication.Encrypt(ticket);
//3) 创建cookie
HttpCookie cookie =new HttpCookie(FormsAuthentication.FormsCookieName,cookieStr);
if(PersistCookie.Checked) //如果用户选择了保存密码
cookie.Expires=ticket.Expiration;//设置cookie有效期
//cookie存放路径
cookie.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(cookie);
// 4) do a redirect
string strRedirect;
strRedirect=Request["ReturnUrl"];
if(strRedirect==null)
strRedirect="ceshi.aspx";
Response.Redirect(strRedirect,true);
}
else
Response.Write("<script language='javascript'>alert('用户名称或密码错误!')</script>");
}
private bool ArraysEqual(byte[] array1,byte[] array2)
{
bool bResult = true;
if(array1==null)
throw new ArgumentNullException("array1");
if(array2==null)
throw new ArgumentNullException("array2");
if(array1.Length == array2.Length)
{
for(int i=0;i<array1.Length;i++)
{
if(array1[i]!=array2[i])
{
bResult = false;
break;
}
}
}

return bResult;
}
private bool AuthenticateUser(string strUserName, string strUserPass)
{
SqlConnection con = new SqlConnection();
con.ConnectionString = ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
con.Open();
string strSql = "sp_getuserdetails";
SqlCommand com = new SqlCommand(strSql,con);
com.CommandType = CommandType.StoredProcedure;
SqlParameter sqlpUser = new SqlParameter("@acctname",SqlDbType.NVarChar,64);
sqlpUser.Value = tbName.Text;
SqlParameter sqlpPasshash = new SqlParameter("@passhash",SqlDbType.NVarChar,50);
sqlpPasshash.Direction = ParameterDirection.Output;
SqlParameter sqlpPasssalt = new SqlParameter("@passsalt",SqlDbType.NVarChar,50);
sqlpPasssalt.Direction = ParameterDirection.Output;
com.Parameters.Add(sqlpUser);
com.Parameters.Add(sqlpPasssalt);
com.Parameters.Add(sqlpPasshash);
com.ExecuteNonQuery();
string hash = com.Parameters["@passhash"].Value.ToString();
string salt = com.Parameters["@passsalt"].Value.ToString();

bool bExist = false;
if(hash==null||salt==null)
bExist = false;
else
{
byte[] saltBits = Convert.FromBase64String(salt);
byte[] hashBits = Convert.FromBase64String(hash);
byte[] passBits = Encoding.Unicode.GetBytes(strUserPass);

HashAlgorithm hashAlg = SHA1.Create();
using (CryptoStream cs = new CryptoStream(Stream.Null, hashAlg, CryptoStreamMode.Write))
{
cs.Write(passBits, 0, passBits.Length);
cs.Write(saltBits, 0, saltBits.Length);
cs.FlushFinalBlock();
cs.Close();
}

byte[] digest = hashAlg.Hash;
if (ArraysEqual(digest, hashBits))
bExist = true;
else
bExist = false;
}
con.Close();
return bExist;
}
}
}
然后创建一个叫ceshi的页面来验证cookie。
<form id="Default" method="post" runat="server">
<asp:Label id="Label1" style="Z-INDEX: 101; LEFT: 215px; POSITION: absolute; TOP: 79px" runat="server">用户名称:</asp:Label>
<asp:Label id="Label2" style="Z-INDEX: 102; LEFT: 220px; POSITION: absolute; TOP: 136px" runat="server">身份:</asp:Label>
<asp:Label id="lbUser" style="Z-INDEX: 103; LEFT: 350px; POSITION: absolute; TOP: 79px" runat="server"></asp:Label>
<asp:Label id="lbSf" style="Z-INDEX: 104; LEFT: 355px; POSITION: absolute; TOP: 133px" runat="server"></asp:Label>
<asp:Button id="btnLogout" style="Z-INDEX: 105; LEFT: 261px; POSITION: absolute; TOP: 192px" runat="server" Text="注销" Width="101px" onclick="btnLogout_Click"></asp:Button>
</form>
编码:
protected void Page_Load(object sender, EventArgs e)
{
// 在此处放置用户代码以初始化页面
lbUser.Text = User.Identity.Name;
if (User.IsInRole("Admin"))
lbSf.Text = "Admin";
else
lbSf.Text = "User";
}

protected void btnLogout_Click(object sender, EventArgs e)
{
FormsAuthentication.SignOut();//注销
Response.Redirect("login2.aspx", true);

}
大概就是这样,有兴趣的朋友可以学习下,现在的都是我自己理解的,有错误的话请指出。
ASP.NET身份模拟:
缺省情况下,ASP.NET应用程序以本机的ASPNET帐号运行,该帐号属于普通用户组,权限受到一定的限制,以保障ASP.NET应用程序运行的安全。但是有时需要某个ASP.NET应用程序或者程序中的某段代码执行需要特定权限的操作,比如某个文件的存取,这时就需要给该程序或相应的某段代码赋予某个帐号的权限以执行该操作,这种方法称之为身份模拟(Impersonation)。
在ASP.NET应用程序中使用身份模拟一般用于资源访问控制,主要有如下几种方法:
模拟IIS认证帐号:
<IDENTITY impersonate="true" />
在某个ASP.NET应用程序中模拟指定的用户帐号
<IDENTITY impersonate="true"
userName="accountname" password="password" />
在代码中模拟IIS认证帐号
在代码中模拟指定的用户帐号
在代码中使用身份模拟更加灵活,可以在
指定的代码段中使用身份模拟,在该代码
段之外恢复使用ASPNET本机帐号。该方
法要求必须使用Windows的认证身份标识。ASP.NET进程标识:
使用权限最小的账户 使用权限最少的帐户可以减少与进程攻击相关的威胁。避免作为SYSTEM运行,域控制器和ASP.NET 进程帐户。 一般情况下,不建议在域控制器上运行Web 服务器,因为对服务器的攻击就是对域的攻击。
使用默认ASPNET 帐户已将本地ASPNET 帐户明确配置为使用尽可能最少的权限集运行ASP.NET Web 应用程序。如果可能,尽量使用ASPNET。
存储机密:
Web 应用程序通常需要存储机密。需要妥善保管这些机密,以防止不道德的管理员和有恶意的Web 用户进行访问。
机密的典型示例包括:
SQL 连接字符串。一个常见的错误是以纯文本形式存储用户名和密码。
Web.config 中的固定标识。
1,Machine.config 中的进程标识。
2,用于安全地存储数据的密钥。
3,用于根据数据库进行表单身份验证的密码。
在ASP.NET 中存储机密的选项:
NET Web 应用程序开发人员可以使用多种方法来存储机密。
它们包括:
• .NET 加密类。.NET 框架包含可用于加密和解密的类。这些方法要求安全地存储加密密钥。
• 数据保护API (DPAPI)。DPAPI 是一对Win32 API,它使用从用户密码派生的密钥对数据进行加密和解密。在使用DPAPI 时,您并不需要进行密钥管理。操作系统对作为用户密码的密钥进行管理。
• COM+ 构造函数字符串。如果应用程序使用服务组件,则可以在对象构造字符串中存储机密。该字符串以明文形式存储在COM+ 目录中。
• CAPICOM。这是一个Microsoft COM 对象,它提供对基础加密API 基于COM 的访问。
• 加密API。这些API 是执行加密和解密的低级Win32 API。
使用加密:
• 使用名称空间
System.Security.Cryptography:该命名空间提供加密服务,包括安全的数据编码和解码,以及许多其他操作,例如散列法、随机数字生成和消息身份验证。
• 使用RNGCryptoServiceProvider,代替System.Random。
使用最佳实践:
• 如果应用程序使用表单身份验证,而且在用户身份验证中需要考虑性能问题,则检索角色列表并将其存储此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。