在vs.net默认带的web.config文件关于froms验证的部分,authorization节的提示中只有users和roles两个配置选项,其实这个节有三个选项的,大家可以看sdk文档里的介绍,少的那个是verbs选项,“一个逗号分隔的 HTTP 传输方法列表,这些 HTTP 传输方法被授予对资源的访问权限。注册到 ASP.NET 的谓词为 GET、HEAD、POST 和 DEBUG”。
可是我今下午去图书馆查书,甚至找了两本关于.net安全性编程的专著,也没有发现关于对这个的详细的说明,因此在这里我只能和大家讨论一下get和post了。
对get和post相信大家都很熟悉。用户可以请求服务器向其发送一个诸如web页面、图片或mp3文件这样的资源。这被称为get请求。用户请求服务器执行一些处理,以便生成响应。这被称为post请求。如果我们在web.config里加这么一句:
<deny users="?" verbs="post"></deny>
这表示禁止匿名用户提出post请求,也就是说,用户可以浏览页面,但当用户企图向服务器发送数据以获得响应的时候,该请求不会被执行,当然页面在这个时候会被定向到login.aspx。如果在这里使用的是verbs="get",那么用户连浏览的权力都没有了。
我想这个在很多地方都可以派上用场,比如一个论坛,未登陆用户也可以浏览帖子(get),但是如果想发表回复或帖新话题(post),则必须要登陆才可以。
最后把和forms验证配置节有关的sdk文档的地址贴出来,方便查看。
<authentication> 元素
ms-help://MS.NETFrameworkSDKv1.1.CHS/cpgenref/html/gngrfauthenticationsection.htm
<forms> 元素
ms-help://MS.NETFrameworkSDKv1.1.CHS/cpgenref/html/gngrfforms.htm
<credentials> 元素
ms-help://MS.NETFrameworkSDKv1.1.CHS/cpgenref/html/gngrfcredentials.htm
<user> 元素
ms-help://MS.NETFrameworkSDKv1.1.CHS/cpgenref/html/gngrfuser.htm
<authorization> 元素
ms-help://MS.NETFrameworkSDKv1.1.CHS/cpgenref/html/gngrfauthorizationsection.htm
在web.config里做好设定,在ui层做好反应机制,接下来就可以把自己的web程序交给Forms验证来保护了,下面就来看Forms验证发挥作用的过程。
首先,每当我们发送一个页面的请求,都会激发数个应用程序级(Application)的事件,其中和用户验证有关的是AuthenticateRequest,打开Global.asax.cs就可以看到它
void Application_AuthenticateRequest(Object sender, EventArgs e)
每次请求发送过来,都会进入这个处理方法里面,我们可以加入以下的代码,然后在第一句的位置设置断点,来查看一下他们的值,这样就会有清晰的认识啦
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//获得请求发送者
HttpApplication app=(HttpApplication)sender;
// 检查是否通过验证
if(app.Request.IsAuthenticated)
{
//在这里查看类型
if(app.Context.User is System.Security.Principal.GenericPrincipal)
if(app.Context.User.Identity.Name=="xx")
{}
}
}
从第一句那里就设断点,然后用f10步进查看各个的值,也可以把那些重要的添加到监视,比如app.Context.User和app.Context.User.Identity
先说一下app.Context.User就相当于我们在ui层里使用的this.User。
当请求第一次发过来的时候,也就是没有通过验证的时候,这个时候是没有调用FormsAuthentication.SetAuthCookie()的,注意看app.Context.User“未定义的值”,而app.Context.User.Identity根本不存在。
然后进入login.aspx, 填好信息后,提交,检查信息,若有问题,则打回去,否则就调用FormsAuthentication.SetAuthCookie将用户凭证赋给当前用户
通过验证后,再次进入该方法,这时监视窗口里的信息为:
app.Context.User
{System.Security.Principal.GenericPrincipal}
System.Security.Principal.IPrincipal
app.Context.User.Identity
{System.Web.Security.FormsIdentity}
System.Security.Principal.IIdentity
很明显,在获得用户凭证后,app.Context.User是GenericPrincipal类型,而app.Context.User.Identity是FormsIdentity类型。
GenericPrincipal的Identity返回一个实现IIdentity接口的对象,具体返回的值就看你在web.config里的设定,在这里当然就是返回FormsIdentity
再接着往下想,一开始我们是用app.Request.IsAuthenticated检查,通过反射器查看的该方法的实现为:
public bool IsAuthenticated
{
get
{
if ((this._context.User != null) && (this._context.User.Identity != null))
{
return this._context.User.Identity.IsAuthenticated;
}
return false;
}
}
这就是为什么不直接用app.Context.User.Identity.IsAuthenticated的原因,因为它在里面替我们检查了app.Context.Use是否已经存在,而实际上,如果app.Context.User存在了,那肯定是通过验证了,即调用了FormsAuthentication.SetAuthCookie()方法,下面是其实现:
public static void SetAuthCookie(string userName, bool createPersistentCookie)
{
FormsAuthentication.Initialize();
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
}
public static void SetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath)
{
FormsAuthentication.Initialize();
HttpContext.Current.Response.Cookies.Add(FormsAuthentication.GetAuthCookie(userName, createPersistentCookie, strCookiePath));
}
我们大多使用的是重载的第一个方法,它在里面调用了第二个方法,最关键的就是设定cookie那句啦,而不难想到,在Initialize()肯定是有对app.Context.User的初始化。
汗……我发现我的表达能力越来越差了,再说下去恐怕自己都糊涂了,就在这里结尾吧。下面是我的总结:
当用户没有通过验证时,一切都没有发生,根据web.config里各location的权限设定来加以保护,一旦用户填写完login.aspx的表单提交,并且通过验证,则调用FormsAuthentication.SetAuthCookie(),在这个方法里,设定了表示用户凭证的cookie,并且重新设定了Context的实例,以后就可以直接通过该Context的实例获得对实现IPrincipal接口的GenericPrincipal对象和实现IIdentity接口的FormsIdentity对象的访问。
絮絮叨叨的写了这么多,不知大家明白了没有……其实认识这个过程,关键是为使用自己的验证体系做准备,当我们要自己实现IPrincipal和IIdentity来使用自定义的验证体系时,比如分别是REDPrincipal和REDIdentity,那就要考虑转换的问题,因为默认是转成了GenericPrincipal和FormsIdentity。而转换的最佳地点就是在Application_AuthenticateRequest()方法里。
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpApplication app=(HttpApplication)sender;
if(app.Request.IsAuthenticated)
{
if(!(app.Context.User is Business.RedPrincipal))
{
app.Context.User=new Business.RedPrincipal(app.Context.User.Identity.Name);
}
}
}
1.使用Forms验证存储用户自定义信息
Forms验证在内部的机制为把用户数据加密后保存在一个基于cookie的票据FormsAuthenticationTicket中,因为是经过特殊加密的,所以应该来说是比较安全的。而.net除了用这个票据存放自己的信息外,还留了一个地给用户自由支配,这就是现在要说的UserData。
UserData可以用来存储string类型的信息,并且也享受Forms验证提供的加密保护,当我们需要这些信息时,也可以通过简单的get方法得到,兼顾了安全性和易用性,用来保存一些必须的敏感信息还是很有用的。
下面来看怎么使用UserData,然后会给出一个实际使用的例子。
//创建一个新的票据,将客户ip记入ticket的userdata
FormsAuthenticationTicket ticket=new FormsAuthenticationTicket(
1,userName.Text,DateTime.Now,DateTime.Now.AddMinutes(30),
false,Request.UserHostAddress);
//将票据加密
string authTicket=FormsAuthentication.Encrypt(ticket);
//将加密后的票据保存为cookie
HttpCookie coo=new HttpCookie(FormsAuthentication.FormsCookieName,authTicket);
//使用加入了userdata的新cookie
Response.Cookies.Add(coo);
下面是FormsAuthenticationTicket构造函数的重载之一的方法签名
public FormsAuthenticationTicket(
int version,
string name,
DateTime issueDate,
DateTime expiration,
bool isPersistent,
string userData
);
参数
version
版本号。
name
与身份验证票关联的用户名。
issueDate
Cookie 的发出时间。
expiration
Cookie 的到期日期。
isPersistent
如果 Cookie 是持久的,为 true;否则为 false。
userData
将存储在 Cookie 中的用户定义数据
使用userdata也很简单,FormsIdentity的Ticket属性就提供了对当前票据的访问,获得票据后就可以用UserData属性访问保存的信息,当然是经过解密的。
((System.Web.Security.FormsIdentity)this.Context.User.Identity).Ticket.UserData
下面是一个具体的应用。
由于Forms验证是通过cookie来进行的,它需要传递一个票据来进行工作。虽然票据是加密的,里面的内容不可见,但这并不能阻止别人用一个假冒的身份使用票据(就像我们可以拿别人的钥匙去开别人的锁),比较常见的就是不同ip的用户在不安全通道截获了这个票据,然后使用它进行一些安全范围外的活动。
解决这个问题的办法之一就是使用SSL来传递信息。
但是如果不能使用SSL呢?我们可以判断ip和票据是否匹配,如果发出请求的ip是初次产生票据的ip,则没有问题,否则就销毁这个票据。
为此,我们需要在一开始处理登录时将用户的ip保存起来,这样就可以在以后的请求中随时验证后继请求的ip是否和初始ip相同。保存这个敏感ip的最佳场所当然是UserData啦,而验证的时机则是在AuthenticateRequest事件发生时,即Global.aspx.cs中定义的处理此事件的Application_AuthenticateRequest方法中。
上面的示例实际上已经是把用户ip保存到了UserData中,下面是验证的过程。
if(this.Request.IsAuthenticated)
{
if(((System.Web.Security.FormsIdentity)this.Context.User.Identity).Ticket.UserData !=this.Request.UserHostAddress)
{
System.Security.Principal.GenericIdentity gi=new System.Security.Principal.GenericIdentity("","");
string[] rolesi={};
System.Security.Principal.GenericPrincipal gpi=new System.Security.Principal.GenericPrincipal(gi,rolesi);
this.Context.User=gpi;
}
}
通过给GenericPrincipal空的GenericIdentity和roles使票据失效,这样将强迫用户重新登录。为了测试这个方法,可以先把条件改为相等,看效果如何 :)
这个方法也有不足之处,具体为
1.使用同一代理的用户将拥有同一个ip,这样就不能防范此类假冒攻击了
2.如果用户使用动态ip,则可能造成正常用户被我们强行销毁票据。
不过总的来说,这个办法还是比较可行的。
2.使用安全特性配合Forms验证进行安全操作。
PrincipalPermissionAttribute可以配合Forms验证进行基于角色或用户的安全验证,该特性不能用于程序集级别。它的作用范围可以是类或具体的方法。来看一个简单的示例。
[PrincipalPermission(SecurityAction.Demand,User="Notus")]
public class Test : BasePage
{
private void Page_Load(object sender, System.EventArgs e)
{
try
{
this.sayHello();
this.sayHello2();
}
catch(Exception ex)
{
Response.Write(ex.ToString());
}
}
private void sayHello()
{
Response.Write("hello world!");
}
private void sayHello2()
{
Response.Write("hello PrincipalPermissionAttribute!");
}
#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
注意这个例子一开始是作用于整个类的,生成后执行,如果当前用户不是Notus,就会发生异常System.Security.SecurityException,提示对主体权限的请求失败。反之,则可以顺利访问,并输出两个hello world!,注意是两个。现在的安全作用范围是整个类。
接下来我们改一下特性的作用范围。将特性声明移到sayHello2方法上面,重新编译后运行,就会发现程序在运行到sayHello2方法后抛出了System.Security.SecurityException。这说明现在安全作用范围缩小到了方法级别。
该特性可以通过设置User和Role来进行基于用户和角色的安全保护。另外其使用的第一个参数是SecurityAction枚举,该枚举设置了具体的保护级别或措施。像我们现在使用的这个Demand,是要求调用堆栈中的所有高级调用方都已被授予了当前权限对象所指定的权限。
下面是msdn给的示例
示例
下面的示例说明可以如何以声明方式使用 PrincipalPermission 要求当前用户是 Bob 并且属于 Supervisor 角色。
[PrincipalPermissionAttribute(SecurityAction.Demand, Name="Bob",
Role="Supervisor")]下面的示例说明如何要求当前用户的身份是 Bob,与角色成员条件无关。
[PrincipalPermissionAttribute(SecurityAction.Demand, Name="Bob")]
下面的示例说明如何仅要求对用户进行身份验证。
[PrincipalPermissionAttribute(SecurityAction.Demand, Authenticated=true)]
再要说一下的是,这里面的User和Role是可以和Forms验证集成的,据此,我们可以在一些重要的类或方法中使用PrincipalPermissionAttribute,以将自己的程序武装到家。
而实际上,该特性的作用远不止这些,更详细的信息可以查阅msdn。
浙公网安备 33010602011771号