Asp.Net事务和异常处理

第一篇:Asp.Net安全性
学习MSDN的WEBCAST,感觉单纯看一遍意义不大,做个笔记,以备不时只需查找方便。代码部分本人在XP+VS2005+SQL2005测试通过,请大家指教。
Asp.Net事务和异常处理:
一。什么是事务处理?
事务处理是一组组和成逻辑工作单元的数据库操作,虽然系统中可能会出错,但事务将控制和维护每个数据库的一致性和完整性。
如果在事务过程中没有遇到错误,事务中的所有修改都将永久成为数据库中的一部分,如果遇到错误,则不会对数据库进行任何修改。
二。事务处理过程。
1. 开始一个事务。进入“事务待命”状态。
2. 在“事务待命”状态,记录事务中改变的数据库记录。此改变不能直接改变数据库中的值,必须先用一个顺序的“事务日志”记录在一边。同时,对于要改变的原始记录加锁,让其它用户无法读和写。如果记录已经被其它事务加锁,则报错。
3. 在“事务待命”,如果用户给出commit transaction命令,则进入“事务拷贝”状态,拷贝所有加锁的记录成备份。
4. 上面3执行完,则进入“事务更新”状态,用“事务日志”中记录一一更新实际的数据库记录。
5. 上面4执行完,则进入“事务结束”状态,释放所有的记录锁,然后抛弃“事务日志”和备份的原数据库记录。
6. 上面5做完后,事务被删除。但是,最为关键的是,事务系统必须执行以下过程:一但数据库由于软件、硬件问题发生故障,重启动后,一旦有事务没正常删除,则:
7. 如果在“事务待命”、“事务结束”状态,则重新从5中结束事务的动作开始执行。
8. 如果在“事务更新”状态,则重新从4开始更新记录,并继续想下执行。结果,虽然系统崩溃过,但事务仍然能正常提交。
三。事务处理的有关事项
•事务处理的关键是在提交事务或者取消事务时,万一系统崩溃了,数据库在再次启动时,仍然需要保持数据可逻辑一致性。
• 应用中包含的事务应当尽量让它“瞬间”完成,避免在比较忙时造成用户进程的互锁。
• Informix、Oracle、DB2等数据库的实际事务处理流程更复杂,目的是具有更好的抵抗系统错误性质,因为事务保护是业务系统安全稳定的最后一道防线。

四。事务处理的方法
1,直接写入SQL。我们来看一个例子。新建一个表,具体过程我就不给出了,大家自己建,也就是达到测试地目的,有个用户名就可以。然后写存储过程:
create procedure [SqlAcTran] As
begin tran
declare @UserInfoError int 
delete from [SqlAction] where username='测试'
select @UserInfoError =@@error
if(@UserInfoError =0)
commit tran
else
rollback tran
go
然后建立一个页面,页面简单之极,只用一个BUTTON来执行命令,然后对BUTTON编程,这里如果对连接语句不明白的话参加我的第一篇:Asp.Net安全性
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;

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

        }


        
protected void btn_Click(object sender, EventArgs e)
        
{
            SqlConnection con 
= new SqlConnection();
            con.ConnectionString
=ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
            con.Open();
            SqlCommand com 
= new SqlCommand();
            com.Connection 
= con;
            com.CommandType 
= CommandType.StoredProcedure;
            com.CommandText 
= "SqlAcTran";
            com.ExecuteNonQuery();
            con.Close();

        }

    }

}



2,Ado.Net事务处理。
在ADO.NET 中,可以使用Connection 和Transaction 对象来控制事务。若要执行事务,请执行下列操作:
• 调用Connection 对象的BeginTransaction 方法来标记事务的开始。
• 将Transaction 对象分配给要执行的Command的Transaction 属性。
• 执行所需的命令。
• 调用Transaction 对象的Commit 方法来完成事务,或调用Rollback 方法来取消事务。
当然ADO.NET事务处理有优点和缺点,运用起来看具体情况了。
• 优点:
– 简单性
– 和数据据事务差不多的快
– 独立于数据库,不同数据库的专有代码被隐藏了
• 缺点:
– 事务不能跨越多个数据库连接
– 事务执行在数据库连接层上,所以需要在事务过程中维护一个数据库连接。
下边我们看一个例子,建立一个页面,同样简单,只需要一个按钮,然后编程:
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;

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

        }


        
protected void btn_Click(object sender, EventArgs e)
        
{
            SqlConnection con 
= new SqlConnection();
            con.ConnectionString
=ConfigurationManager.ConnectionStrings["DSN"].ConnectionString;
            con.Open();
            
//启动一个事务。
            SqlTransaction myTran = con.BeginTransaction();
            
//为事务创建一个命令,注意我们执行双条命令,第一次执行当然成功。我们再执行一次,失败。
            
//第三次我们改其中一个命令,另一个不改,这时候事务会报错,这就是事务机制。
            SqlCommand myCom = new SqlCommand();
            myCom.Connection 
= con;
            myCom.Transaction 
= myTran;
            
try
            
{
                myCom.CommandText 
= "insert into SqlAction values ('测试2','111')";
                myCom.ExecuteNonQuery();
                myCom.CommandText 
= "insert into SqlAction values ('测试3','111')";
                myCom.ExecuteNonQuery();
                myTran.Commit();
                Response.Write(
"成功执行");

            }

            
catch (Exception Ex)
            
{
                myTran.Rollback();
                
//创建并且返回异常的错误信息
                Response.Write(Ex.ToString());
                Response.Write(
"写入数据库失败");
            }

            
finally
            
{
                con.Close();
            }

 
        }



    }

}


3,COM+事务
一般的数据库事务控制要求事务里所做的操作必须在同一个数据库内,这样在出现错误的时候才能回滚(RollBack)到初始状态。这就存在一个问题,在分布式应用程序中,我们往往需要同时操作多个数据库,使用数据库本身的事务处理,很难满足程序对事务控制的要求。在COM+中,提供了完整的事务服务,我们可以利用它来完成在分布式应用程序中的事务控制。
创建参与自动事务的类:
1. 将TransactionAttribute 类应用于您的类,指定组件请求的自动事务类型。事务类型必须是TransactionOption 枚举的成员。
2. 从ServicedComponent 类派生您的类。ServicedComponent 是所有使用COM+ 服务
的类的基类。
3. 用强名称(strong name) 标记(sign) 程序集(assembly),确保程序集包含唯一的密钥对。
4. 在COM+ catalog 中注册包含您的类的程序集.
• 定义一个COM+事务处理的类
[Transaction(TransactionOption.Required)]
public class
DataAccess:System.EnterpriseServices.ServicedCo
mponent
{
}
• TransactionOption枚举类型支持5个COM+值
– Disabled 忽略当前上下文中的任何事务。
– NotSupported 使用非受控事务在上下文中创建组件。
– Required 如果事务存在则共享事务,并且如有必要则创建新事务。
– RequiresNew 使用新事务创建组件,而与当前上下文的状态无关。
– Supported 如果事务存在,则共享该事务。
强名称:
为使该组件正确运行,该组件必须有一个强名称。生成一个强名称,然后使用该强名称对程序集进行签名。为此,请按照下列步骤操作:
• 在命令提示符下,键入sn -k snEnterprise.snk 以创建一个密钥文件。
• 将snEnterprise.snk 复制到您的项目文件夹中。
• 在AssemblyInfo.vc 中,将以下代码添加到其他程序集属性语句之前或之后:[assembly:AssemblyKeyFileAttribute("..\\..\\snEnterprise.snk")]
• 进行保存,然后生成您的项目
• 使用regsvcs.exe将Dll注册到COM+ Services里面
注意事项:
• 确保使用COM+ 服务的所有项目都有一个强名称。
• 使用COM+ 服务的所有类都必须继承服务组件。服务组件位于System.EnterpriseServices 命名空间中。
• 进行调试时,事务在提交或终止前可能会超时。要避免出现超时,请在事务属性中使用一个超时属性。在面的示例中,在完成任何事务时,关联的方法在超时前都有1,200 秒的执行时间。
[Transaction(TransactionOption.Required,timeout:=1200)]
下边做例子,这是VS2005中的,我修改了一晚上才成功。。哭.写个数据库:
use myDatabase
go
create table ComAction
(
    userID 
int identity,
    userName 
varchar(30not null,
    userScore 
int not null
)
insert into ComAction values('张三',100)

首先我们建立一个类库prjEnterprise 添加引用System.EnterpriseService。  然后添加一个类clsEs
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.EnterpriseServices;

namespace prjEnterprise
{
    [Transaction(TransactionOption.Required)]
public class clsES:System.EnterpriseServices.ServicedComponent
    
{
        
public SqlConnection Conn;

        
public void dbAccess(int  pID1, int nScore)
        
{
            
try
            
{
                SqlConnection Conn 
= new SqlConnection("user id=sa;password=;Initial Catalog=myDataBase;Data Source=.;");
                Conn.Open();
                SqlCommand sqlCommand 
= new SqlCommand("UPDATE ComAction SET userScore = " + nScore + " WHERE userID = " + pID1, Conn);
                sqlCommand.ExecuteNonQuery();

                ContextUtil.SetComplete();
                Conn.Close();
            }

            
catch (Exception e)
            
{
                ContextUtil.SetAbort();

                
throw e;
            }

            
finally
            
{

            }

        }

    }

}

然后给程序添加强名(strong name)
1:创建一对密钥
使用sn.exe这个工具
sn –k SnEnterprise.snk
其中SnEnterprise.snk 代表将保存密钥的文件的名称。它的名称可以是任意的,不过习惯上带有.snk后缀名。
2:签名
签名通常是在编译时进行的。签名时,用户可利用C#属性通知编译器应该使用正确的密钥文件对DLL进行签名。要做到这一点用户需要打开工程中的AssemblyInfo.cs文件并进行修改。
 [assembly: ComVisible(true)] 这里的true默认是false需要改,要不不成我就卡到这上一个晚上。
[assembly:AssemblyKeyFile(“..\\..\\SnEnterprise.snk”)]
注:SnEnterprise.snk文件和项目文件在同一个文件夹
注:SnEnterprise.snk文件和项目文件在同一个文件夹
3:编译成DLL
4:使用regsvcs.exe将Dll注册到COM+ Services里面,我的是这样

C:\web study\WebApplication1\WebApplication1\prjEnterprise\bin\Debug>regsvcs prjEnterprise.dll
Microsoft(R) .NET Framework 服务安装实用工具版本 2.0.50727.42
Copyright (c) Microsoft Corporation.  All rights reserved.

警告: 程序集未声明 ApplicationAccessControl 属性。默认情况下启用应用程序安全性。

已安装的程序集:
        程序集: C:\web study\WebApplication1\WebApplication1\prjEnterprise\bin\D
ebug\prjEnterprise.dll
        应用程序: prjEnterprise
        TypeLib: C:\web study\WebApplication1\WebApplication1\prjEnterprise\bin\
Debug\prjEnterprise.tlb

然后在WebApplication1理添加引用。然后添加测试页面

<form id="_03ComPlusAction" method="post" runat="server">
   <asp:Button id="btnCommit1" style="Z-INDEX: 101; LEFT: 240px; POSITION: absolute; TOP: 102px" runat="server" Text="调用1" OnClick="btnCommit1_Click"></asp:Button>
   <asp:Button id="btnCommit2" style="Z-INDEX: 102; LEFT: 241px; POSITION: absolute; TOP: 164px" runat="server" Text="调用2" OnClick="btnCommit2_Click"></asp:Button>
  </form>
后台编程:
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;

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

        }


        
protected void btnCommit1_Click(object sender, EventArgs e)
        
{
            prjEnterprise.clsES myTest 
= new prjEnterprise.clsES();
            
try
            
{
                myTest.dbAccess(
1300);
                Response.Write(
"事务成功!");
            }

            
catch (Exception)
            
{
                Response.Write(
"事务失败!");
            }


        }


        
protected void btnCommit2_Click(object sender, EventArgs e)
        
{
            prjEnterprise.clsES myTest 
= new prjEnterprise.clsES();

            
try
            
{
                myTest.dbAccess(
1,1000000000);
                Response.Write(
"事务成功!");
            }

            
catch (Exception)
            
{
                Response.Write(
"事务失败!");
            }

        }

    }

}

什么是异常处理
• 异常是正在执行的程序所遇到的任何错误情形或者意外行为。
• 很多原因都可以引起异常,例如,代码中错误、操作系统资源不可用、公共语言运行时(common language runtime)中的意外情况等等。
• 然而应用程序能够从上述的一些情况中恢复执行,但是大多数运行时异常是不可恢复的。在这种情况下,需要一种有效的方法来处理这些异常并给调用者提供相同的异常。
用结构化的异常处理方法来处理异常
• 在.NET Web服务中,对异常处理支持的关键点是由try...catch..finally语句提供的。
• 关键字try放在可能抛出异常的普通处理代码块之前。
• 关键字catch放在异常处理代码块之前。
• 关键字finally放在那些经常在异常处理后还需要执行的代码块之前。
• 一旦异常从try代码块中抛出,程序流切换到后面的第一个catch代码块
异常类
• Exception 所有异常对象的基类
• SystemException 运行时产生的所有错误的基类
• IndexOutOfRangeException 当一个数组的下标超出范围时运行时引发
• NullReferenceException 当一个空对象被引用时运行时引发
• InvalidOperationException 当对方法的调用对对象的当前状态无效时,由某些方法引发
• ArgumentException 所有参数异常的基类
• ArgumentNullException 在参数为空(不允许)的情况下,由方法引发
• ArgumentOutOfRangeException 当参数不在一个给定范围之内时,由方法引发
• InteropException 目标在或发生在CLR外面环境中的异常的基类
• ComException 包含COM 类的HRESULT信息的异常
• SEHException 封装win32 结构异常处理信息的异常
优化异常
• 理解异常是一定会发生的
– 大多数的软件系统都不是百分之百可靠的!
– 要站在异常一定可能会发生的角度来编写异常
处理程序,应对程序有可能发生的错误。
– 建立一个良好的异常处理策略
• 处理未预料的异常
– 确保所有程序的入口都使用了try-catch
– 在catch中截获所有的异常
异常处理注意事项
• 当引发异常时,要提供有意义的文本。
• 要引发异常仅当条件是真正异常;也就是当一个正常的返回值不满足时。
• 如果你的方法或属性被传递一个坏参数,要引发一个ArgumentException异常。
• 当调用操作不适合对象的当前状态时,要引发一个InvalidOperationException异常。
• 要引发最适合的异常。
• 要使用链接异常,它们允许你跟踪异常树。
• 不要为正常或预期的错误使用异常。
• 不要为流程的正常控制使用异常。
• 不要在方法中引发NullReferenceException或IndexOutOfRangeException异常。
异常处理技术
• 记录异常
– 在文件中记录异常
– 在数据库中记录异常
– 在eventlog中记录异常
• 发送email通知异常
• 异常产生时,用友好(user-friendly)的方式通知用户
处理错误
• Page_Error事件
• Application_Error事件
• 利用配置文件,自定义错误页面
– <customErrors defaultRedirect="url" mo
de="RemoteOnly">
<error statusCode="code" redirect="url
"></error>
</customErrors>

异常的例子我就不说了,大家知道的一定比我多,网上也可以搜到很多相关的例子来。

 

ASP.NET安全性

2007-08-26 20:59 by Yeemio, 931 visits, 收藏, 编辑

第二篇: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,表单身份验证是指以下系统:将未经身份验证的请求重定向到一个超文本标记语言(HTML) 表单,使用能够在其中键入他们的凭据。在用户提供凭据并提交该表单后,应用程序对请求进行身份验证,然后系统Cookie的形式发出身份验证票证。此Cookie 包含凭据或用于重新获取标识的密钥。浏览器的后续请求自动包含此Cookie。
2,在Windows 身份验证中,IIS 执行身份验证,并将经过身份验证的标记传递给ASP.NET 工作进程。使用Windows 身份验证的优点是它需要的编码最少。在将请求传递给ASP.NET 之前,您可能需要使用Windows 身份验证来模拟IIS 进行验证的Windows 用户帐户。
3,Passport 身份验证是Microsoft 提供的集中式身份验证服务,它为成员站点提供单一登录和核心配置文件服务。通常,当您需要跨越多个域的单一登录功能时,将使用Passport 身份验证。
4,当Web 应用程序不需要任何安全功能时,将使用默认身份验证;此安全提供程序需要匿名访问。在所有身份验证提供程序中,默认身份验证为应用程序提供了最高的性能。当您使用自己的自定义安全模块时,也可以使用此身份验证提供程序。


表单身份验证的开发步骤。

 

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。

使用最佳实践:
• 如果应用程序使用表单身份验证,而且在用户身份验证中需要考虑性能问题,则检索角色列表并将其存储
在身份验证票中。
• 如果使用表单身份验证,则始终给每个请求创建一个主体并将其存储在上下文中。
• 如果角色太多而无法存储在身份验证Cookie 中,则使用全局应用程序缓存来存储角色。
• 不要创建权限最少的自定义帐户来运行ASP.NET。而是应更改ASPNET 帐户密码,并在应用程序需要
访问的任何远程Windows 服务器上创建重复帐户。
• 如果必须创建自定义帐户以运行ASP.NET,请使用权限最少的用户。
• 如果主要考虑管理问题,请使用权限最少的域帐户。
• 如果使用本地帐户,则必须在Web 应用程序需要访问的任何远程计算机上创建重复帐户;如果应用程序需要访问非信任域中的资源或者防火墙禁止Windows 身份验证,则必须使用本地帐户。
• 不要使用本地SYSTEM 帐户来运行ASP.NET。
• 不要给ASPNET 帐户授予“充当操作系统的一部分”权限。
• 在以下情况中使用SSL:
– 在浏览器和Web 服务器之间传送安全敏感信息时。
– 使用基本身份验证(以保护凭据)时。
– 使用表单身份验证进行身份验证(与个性化相对)时。
• 避免使用纯文本形式存储机密。

posted on 2011-08-23 20:32  落叶十九  阅读(34)  评论(0编辑  收藏  举报