Security Tutorials系列文章第七章:User-Based Authorization(下)

 

首先打开Membership文件夹里的UserBasedAuthorization.aspx页面,添加一个名为FilesGrid的GridView控件.在智能标签里点“Edit Columns”以打开Fields对话框,在对话框里不要点“Auto-generate fields” checkbox,然后,添加一个Select按钮、一个Delete按钮以及2个BoundFields(Select 和 Delete按钮位于CommandField下面)。设Select按钮的SelectText属性为“View”,在将第一个BoundField的HeaderText 和 DataField属性设为“Name”.将第二个BoundField的HeaderText设置为“Size in Bytes”,而将DataField属性设为“Length”,此外DataFormatString属性为“{0:N0}” ,HtmlEncode属性为False.

   设置完后点OK关闭Fields对话框.在属性窗口里将GridView的DataKeyNames属性设置为FullName。这样,该GridView的声明代码就和下面的差不多:

<asp:GridView ID="FilesGrid" DataKeyNames="FullName" runat="server" AutoGenerateColumns="False">   
<Columns>         
    <asp:CommandField SelectText="View" ShowSelectButton="True"/>       
    <asp:CommandField ShowDeleteButton="True" />          
    <asp:BoundField DataField="Name" HeaderText="Name" />             
    <asp:BoundField DataField="Length" DataFormatString="{0:N0}" HeaderText="Size in Bytes"      HtmlEncode="False" />      

</Columns>
</asp:GridView>

接下来,我们要写代码在某个文件夹里检索文件并绑定到GridView,在页面的Page_Load的事件处理器里添加如下的代码:
protected void Page_Load(object sender, EventArgs e)
{    
if (!Page.IsPostBack)    
{          
   string appPath = Request.PhysicalApplicationPath;           
   DirectoryInfo dirInfo = new DirectoryInfo(appPath);           

   FileInfo[] files = dirInfo.GetFiles();           
   FilesGrid.DataSource = files;         
   FilesGrid.DataBind();     
}
}

上述代码使用DirectoryInfo class类来获取应用程序根目录下的文件列表.而GetFiles()方法将所有的文件以FileInfo objects的形式返回数组,然后绑定到GridView,该FileInfo object有诸如Name, Length, 以及IsReadOnly等的属性.如代码所示,GridView仅仅显示了其Name 和 Length属性.

注意:DirectoryInfo 和 FileInfo类属于System.IO命名空间.因此你在使用这些类时要么加命名空间前缀,要么将命名空间导入类文件(比如:using System.IO)

花点时间来测试页面,它将会将根目录下的文件显示出来,点“View” 或 “Delete”按钮都会产生页面回传,但不会真的发生什么动作,因为我们还没有创建相关的事件处理器.


图7:

我们需要将选中文件的内容显示出来.重新打开Visual Studio,在GridView上添加一个名为FileContents的TextBox.设其TextMode属性为MultiLine,Width和Rows属性分别为“95%”和10.

<asp:TextBox ID="FileContents" runat="server" Rows="10" TextMode="MultiLine" Width="95%">

</asp:TextBox>

接下来为GridView的SelectedIndexChanged事件添加如下的代码:

protected void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{     
// Open the file and display it     
string fullFileName = FilesGrid.SelectedValue.ToString();     
string contents = File.ReadAllText(fullFileName);   
FileContents.Text = contents;
}

代码使用GridView的SelectedValue属性来判断所选文件的完整文件名.在内部,通过引用DataKeys来获得SelectedValue,因此我们务必要将GridView的 DataKeyNames属性设置为“Name”.我们使用File class类来将所选文件的内容读入一个字符串,再赋值给FileContents TextBox的Text属性,这样就将所选文件的内容显示在页面上了.


图8

注意:
    如果你查看的内容包含HTML标记,然后尝试查看或删除一个文件话,你将收到一个HttpRequestValidationException错误.因为在回传时TextBox的内容将发回到web server,默认情况下,任何时候,当回传的内容存在潜在危险时,比如包含HTML标识,当检测到时就会引发一个HttpRequestValidationException错误.为避免这个报错,我们在页面的@Page声明里添加ValidateRequest="false".关于request validation的好处以及如何禁用它的更多详情请参阅文章《Request Validation – Preventing Script Attacks》

最后为GridView的RowDeleting事件添加如下的代码:

protected void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{    
string fullFileName = FilesGrid.DataKeys[e.RowIndex].Value.ToString();    
FileContents.Text = string.Format("You have opted to delete {0}.", fullFileName);     
// To actually delete the file, uncomment the following line     
// File.Delete(fullFileName);
}

代码仅仅将要删除的文件的完整文件名显示在FileContents TextBox里,并没有真正的删除文件

图9


在第一步里我们禁止匿名用户访问Membership文件夹里的文件,为了更好的进行演示,我们允许所有的匿名用户访问UserBasedAuthorization.aspx页面,只是匿名用户访问时页面提供的功能有限.为了使该页面对所有的用户开放,在Membership文件夹里的Web.config文件里添加 <location>元素:

<location path="UserBasedAuthorization.aspx">    
<system.web>         
<authorization>               
    <allow users="*" />         
</authorization>     
</system.web>
</location>

添加<location>元素完毕后,我们注销后匿名登录,就可以访问UserBasedAuthorization.aspx页面了.

当前,任何的匿名和认证的用户都可以访问UserBasedAuthorization.aspx页面,并查看和删除文件。让我们使认证用户才可以查看内容,并只有Tito可以删除文件.要达到这样的目的,我们可以通过显式声明的方式或编程的方式,或2种方法结合的方式来达到目的.我们使用显式声明的方式限制用户对文件内容的查看,而使用编程的方式限制对文件的删除.

Using the LoginView Control

正如我们在前面的文章里看到的那样,当需要分别向匿名和认证用户显示不同的内容时LoginView控件是很有用的,并可以对匿名用户屏蔽掉某些功能.因为匿名用户无法看到或删除文件,因此我们只需要在认证用户访问时显示FileContents TextBox.为此,在页面里添加一个LoginView控件,id为LoginViewForFileContentsTextBox,再将FileContents TextBox的声明代码剪切到LoginView控件的LoggedInTemplate里.


<asp:LoginView ID=" LoginViewForFileContentsTextBox " runat="server">     

<LoggedInTemplate>          
   <p>              
   <asp:TextBox ID="FileContents" runat="server" Rows="10"     TextMode="MultiLine" Width="95%"></asp:TextBox>         
   </p>     
</LoggedInTemplate>
</asp:LoginView>

LoginView的templates里的Web控件不能直接在后台代码里进行引用。比如,id为FilesGrid的GridView的SelectedIndexChanged 和 RowDeleting事件处理器直接引用FileContents TextBox,如下:

FileContents.Text = text;

这样做是无效的.因为放置到LoggedInTemplate里的FileContents TextBox是不能直接引用的,我们必须通过FindControl("controlId")方法来编程引用,对上面2个事件处理器进行更新,来这样进行引用:

TextBox FileContentsTextBox = LoginViewForFileContentsTextBox.FindControl("FileContents") as TextBox;

FileContentsTextBox.Text = text;

完成后,匿名访问该页面.如图10所示,FileContents TextBox就没有显示出来了,而“View” LinkButton依然可见.

图10


对匿名用户隐藏“View”按钮的一种方法是将其转换为一个TemplateField,这样将生成一个包含“View” LinkButton声明代码的模板.我们然后在模板里添加一个LoginView控件并将一个LinkButton放置在LoginView的LoggedInTemplate里,这样就达到了对匿名用户隐藏“View” 按钮的目的.为此,在GridView的智能标签里点“Edit Columns”打开Fields对话框,然后选中Select按钮,再点击“Convert this field to a

TemplateField” ,这样将使该字段的声明代码由<asp:CommandField SelectText="View" ShowSelectButton="True"/>改成
<asp:TemplateField ShowHeader="False">    
<ItemTemplate>         
   <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"                CommandName="Select" Text="View">
    </asp:LinkButton>    
</ItemTemplate>
</asp:TemplateField>

这时,我们可以向TemplateField添加一个LoginView,如下的代码使“View” LinkButton只对认证用户可见:

<asp:TemplateField ShowHeader="False">     
<ItemTemplate>         
    <asp:LoginView ID="LoginView1" runat="server">              
      <LoggedInTemplate>                   
        <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"                         

CommandName="Select" Text="View">
        </asp:LinkButton>               
      </LoggedInTemplate>          
    </asp:LoginView>    
</ItemTemplate>
</asp:TemplateField>

如图11所示,最终结果还不太完美,因为虽然“View” LinkButtons隐藏了但“View”这一列还是显示出来了.我们将在下一节看如何将这一列完全隐藏起来(而不仅是将LinkButton隐藏起来)


图11


Programmatically Limiting Functionality

在某些情况下,仅仅通过显式声明的方式来限制页面功能还是不够的.比如有些功能时否可用不是仅仅通过判断用户是匿名还是认证用户那么简单.在这种情况下,就要通过编程的方式来显示或隐藏各种用户界面了.为了通过编程的方式限制页面功能,我们要解决2个问题:

1.判断访问页面的用户是否可以使用某个功能

2.根据用户是否有权使用某功能来改变用户界面

为了对上述2个问题进行探讨,我们只允许Tito从GridView删除文件.第一个问题是判断当前用户是否是Tito,有了判断结果后再决定隐藏(或显示)GridView的Delete列.GridView的列可以通过其Columns属性来访问,只有当其Visible属性为true(默认值)时该列才会显示出来.

在将数据绑定到GridView前,先在Page_Load事件处理器里添加如下的代码:
// Is this Tito visiting the page?
string userName = User.Identity.Name;
if (string.Compare(userName, "Tito", true) == 0)    
// This is Tito, SHOW the Delete column     
FilesGrid.Columns[1].Visible = true;
else     
// This is NOT Tito, HIDE the Delete column    
FilesGrid.Columns[1].Visible = false;

如我们在前面的文章《An Overview of Forms Authentication》里探讨的那样,User.Identity.Name返回的时用户身份的名称.如果是Tito访问页面那么GridView的第二列的Visible属性就为true;否则就设置为false.最终结果是只要访问者不是Tito,比如匿名用户或其他认证用户,Delete列就不会显示出来(如图12);然而,如果是Tito访问则将Delete列显示出来(如图13).


图12

图13


第四步:Applying Authorization Rules to Classes and Methods

在第三步里我们禁止匿名用户访问一个文件的内容,同时只允许Tito删除文件.为此,我们同时使用了显式声明和编程2种技术来对未被授权的用户隐藏相应的用户界面.对本例而言,可能隐藏用户界面是直观易懂的,但对其他更复杂的站点而言又如何呢?它们可能有多种方式来执行相同的功能。对未授权的用户限制页面功能时,如果我们忘记隐藏或禁用所有的用户界面元素呢?

确保未授权的用户不能使用某个功能的一个简单办法是对该类或方法用PrincipalPermission特性进行修饰.当.NET runtime使用一个类或执行其中的一个方法时,它要检查以确保当前安全内容(current security context)有权使用某类或执行某个方法.PrincipalPermission提供了一种机制,通过该机制我们就可以定义规则.

让我们做个演示,对GridView的SelectedIndexChanged 和 RowDeleting事件处理器分别运用PrincipalPermission特性,分别禁止匿名用户和除Tito外的认证用户使用.我们要做的仅仅是分别在相应的函数定义上添加恰当的特性:

[PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
protected void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{      ... }

[PrincipalPermission(SecurityAction.Demand, Name="Tito")]
protected void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{      ... }


为SelectedIndexChanged事件处理器添加的特性指出只有认证用户才可以执行该事件处理器;而RowDeleting事件处理器上添加的特性指出只有Tito才可以执行.

如果一个不是Tito的认证用户想执行RowDeleting事件处理器或一个匿名用户想执行SelectedIndexChanged事件处理器的话,.NET runtime将触发一个SecurityException异常.


图14


注意:
    要允许多个security contexts访问一个类或方法,每个security contexts要用PrincipalPermission特性对类或方法进行修饰.打个比方,要允许Tito 和 Bruce执行RowDeleting事件处理器,要添加2个PrincipalPermission特性:

[PrincipalPermission(SecurityAction.Demand, Name="Tito")]
[PrincipalPermission(SecurityAction.Demand, Name="Bruce")]

除了ASP.NET页面外,很多应用程序都包括很多层,比如Business Logic 和Data Access层,这些层典型的都以类库(Class Libraries)的形式执行,包含了很多的类和方法来执行业务逻辑和数据相关的功能.要对这些层应用授权规则的话,PrincipalPermission特性是很有用的.运用PrincipalPermission特性对类和方法定义授权规则的更多详情,请参阅Scott Guthrie的博客《Adding Authorization Rules to Business and Data Layers Using PrincipalPermissionAttributes.》


结语:
   本文我们考察如何应用基于用户的授权规则.我们首先考察了ASP.NET的URL authorization framework.对每个请求,ASP.NET引擎的UrlAuthorizationModule都会检查定义在配置文件里的URL authorization规则,以判断当前用户是否有权访问请求的资源.简单的说,URL authorization可以很方便地对某个页面或某个文件夹里的所有页面定义授权规则.

URL authorization framework是基于page-by-page的原则施加授权规则的.利用URL authorization,用户要么允许要么禁止访问某个具体的资源.很多情况下,我还需要更精细的授权规则,而不仅仅是判断哪些可以访问哪些不能访问某个页面.有时我们允许所有人访问页面,但是对不同的用户显示不同的数据和提供不同的功能.页面级的功能通常包括隐藏特定的用户界面元素以阻止未经授权的用户使用某种功能.此外,我们还可以使用一些特性来限制某些用户对类或方法的执行.

祝编程愉快!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值