最近做一个SSO,使用Form认证方式,结果出了一个很让我郁闷的问题.先描述下问题:
用户登陆时,可以选择是否保存cookie,以便以后直接访问,如果选择否,则在不活动指定时间后,将自动注销.
Web.config中的设置如下:
<authentication mode="Forms">
<forms name=".SFCMS" protection="All" timeout="5" loginUrl="login.aspx" slidingExpiration="false" />
</authentication>
这里只解释一下timeout参数,表示如果建立的是非持久性cookie,在不活动多长时间后,将需要重新验证,如果建立的是持久性cookie,那么该参数无效.
登陆时的建立验证cookie的代码如下:
HttpCookie authCookie = FormsAuthentication.GetAuthCookie("wiseman", true);
uthCookie.Expires = DateTime.Now.Add(TimeSpan.FromHours(1));
HttpContext.Current.Response.Cookies.Add(authCookie);
这里需要重点注意的就是FormsAuthentication.GetAuthCookie (string userName,bool createPersistentCookie)函数,在这里两个参数分别表示验证的用户名和是否持久保存cookie,上面我们的代码设置为true,表示将持久保持该cookie,过期时间是一个小时后.
但实际上,效果完全不是如此,用Request.IsAuthenticated你会发现,在很短的时间内,更确切的说是在5分钟以后,也就是我们在config中配置的timeout值,它的值就会变成false.
这意味着,我们在建立验证cookie时设置的过期时间没有起到效果,那么是cookie失效了吗?答案是否定的.在验证失败之后,我们依然可以取得cookie的值,这表示,在客户端建立的cookie没有问题,那么便是服务端的验证票据出了问题.
先来看看.net2.0中GetAuthCookie的代码.
{
FormsAuthentication.Initialize();
if (userName == null)
{
userName = string.Empty;
}
if ((strCookiePath == null) || (strCookiePath.Length < 1))
{
strCookiePath = FormsAuthentication.FormsCookiePath;
}
FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, string.Empty, strCookiePath);
string text1 = FormsAuthentication.Encrypt(ticket1, hexEncodedTicket);
if ((text1 == null) || (text1.Length < 1))
{
throw new HttpException(SR.GetString("Unable_to_encrypt_cookie_ticket"));
}
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, text1);
cookie1.HttpOnly = true;
cookie1.Path = strCookiePath;
cookie1.Secure = FormsAuthentication._RequireSSL;
if (FormsAuthentication._CookieDomain != null)
{
cookie1.Domain = FormsAuthentication._CookieDomain;
}
if (ticket1.IsPersistent)
{
cookie1.Expires = ticket1.Expiration;
}
return cookie1;
}
FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, string.Empty, strCookiePath);
这句代码表明,建立的验证票据过期时间只与web.config有关,即使我们在获得cookie后,修改其过期时间,也无法对票据的过期产生影响,这样产生的结果就是,客户端的cookie存在,但是服务器端的验证票据已经失效,最终,验证失败.
这里还需要提一下的是createPersistentCookie这个参数,当它为true时,并不表示票据将持久存在,而是表示可以跨越浏览器存在,简单点说就是即使你关了浏览器,只要过期时间不到,验证就依然是有效的.
实际上,很多人和我一样,在.net1.1时就使用了form认证,也使用过GetAuthCookie函数,处理方式也与我上面写的一样,并没出过什么问题,在CommunityServer2.0中,也是这么处理.那在.NET2.0中为什么不行呢?
原因其实很简单,因为在framework中,GetAuthCookie 函数1.1和2.0是不一样的,更准确的说是从2.0RTM版开始,GetAuthCookie函数进行了修改.下面给出.net1.1的GetAuthCookie函数代码:
{
FormsAuthentication.Initialize();
if (userName == null)
{
userName = "";
}
if ((strCookiePath == null) || (strCookiePath.Length < 1))
{
strCookiePath = FormsAuthentication.FormsCookiePath;
}
FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(1, userName, DateTime.Now, createPersistentCookie ? DateTime.Now.AddYears(50) : DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, "", strCookiePath);
string text1 = FormsAuthentication.Encrypt(ticket1);
FormsAuthentication.Trace("ticket is " + text1);
if ((text1 == null) || (text1.Length < 1))
{
throw new HttpException(HttpRuntime.FormatResourceString("Unable_to_encrypt_cookie_ticket"));
}
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, text1);
cookie1.Path = strCookiePath;
cookie1.Secure = FormsAuthentication._RequireSSL;
if (ticket1.IsPersistent)
{
cookie1.Expires = ticket1.Expiration;
}
return cookie1;
}
基本没什么区别,除了这句:
FormsAuthenticationTicket ticket1 = new FormsAuthenticationTicket(1, userName, DateTime.Now, createPersistentCookie ? DateTime.Now.AddYears(50) : DateTime.Now.AddMinutes((double) FormsAuthentication._Timeout), createPersistentCookie, "", strCookiePath);
这里在创建票据时,如果是创建持久性票据,过期时间设定为了50年,那么在50年的范围内,设定的cookie过期时间将影响是否验证成功,所以,如果最上面的代码在.NET1.1中运行的话,则是完全没有问题的.
上面说到CommunityServer2.0有这个问题,实际上CS2.0是基于.NET1.1的系统,所以在1.1环境下没有问题的;虽然它也提供了.NET2.0的solution,但是里面的代码并没有做相应的变动.
这就是为什么网上很多朋友在部署CS2.0时说无法保持自动登陆,而有许多朋友则表示没有问题.部署在1.1环境下的没有问题,而部署在2.0环境下的则会出现上述问题.
在MSDN2005中,并没有对GetAuthCookie函数做过多的说明,这也是造成许多程序员误解的原因,甚至被当做bug被提交.http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=102143
单从功能上来说,我不知道微软的这次改变有什么意义,直接导致的结果是我无法再使用GetAuthCookie,在不修改config文件的前提下,我将无法保持我的form验证能长期存在,唯一的解决方案就是把config文件中的timeout值设大.
据MS宣称这是出于安全的考虑,具体的原因还是不清楚,如果有哪位朋友知道,不妨帖出来让大家看下.
最后再给个问题的解决方案,其实算不上什么解决方案,也就是不用GetAuthCookie函数了,自己处理,如下:
DateTime.Now, DateTime.Now.AddHours(1), true, "", FormsAuthentication.FormsCookiePath);
string ticketEncrypted = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, ticketEncrypted);
authCookie.HttpOnly = true;
authCookie.Path = FormsAuthentication.FormsCookiePath;
authCookie.Secure = FormsAuthentication.RequireSSL;
authCookie.Expires = ticket.Expiration;
HttpContext.Current.Response.Cookies.Add(authCookie);
手动建立票据和cookie,自己设定票据的过期时间,测试通过.