5. 两个不同域名下的应用程序实现SSO
我们已经成功的创建了可以共享的认证Cookie,但是如果Foo站点和Bar站点在不同域名下呢,例如: http://foo.com 和 http://bar.com? 他们不能共享cookie也不能为对方在创建一个可读的cookie.这种情况下每个站点需要创有各自的cookie,调用其它站点的页面来验证用户是否登录.其中一种实现方式就是使用一系列的重定向.
为了实现上述目标,我们需要在每个站点都创建一个特殊的页面(比如:sso.aspx).这个页面的作用就是来检查该域名下的cookie是否存在并返回已经登录用户的用户名.这样其它站点也可以为这个用户创建一个cookie了.下面是Bar.com的sso.aspx:
<%@ Page Language="C#" %>
<script language="C#" runat="server">
void Page_Load()
{
// this is our caller, we will need to redirect back to it eventually
UriBuilder uri = new UriBuilder(Request.UrlReferrer);
HttpCookie c = HttpContext.Current.Request.Cookies[".BarAuth"];
if (c != null && c.HasKeys) // the cookie exists!
{
try
{
string cookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie);
uri.Query = uri.Query + "&ssoauth=" + fat.Name; // add logged-in user name to the query
}
catch
{
}
}
Response.Redirect(uri.ToString()); // redirect back to the caller
}
</script>
这个页面总是重定向回调用的站点.如果Bar.com存在认证cookie,它就解密出来用户名放在ssoauth参数中.
另外一端(Foo.com),我们需要在HTTP Rquest处理的管道中添加一些的代码.可以是Web应用程序的 Application_BeginRequest 事件或者是自定义的HttpHandler或HttpModule.基本思想就是在所有Foo.com的页面请求之前做拦截,尽早的检查验证cookie是否存在:
1. 如果Foo.com的认证cookie已经存在,就继续处理请求,用户在Foo.com登录过
2. 如果认证Cookie不存在就重定向到Bar.com/sso.aspx.
3. 如果现在的请求是从Bar.com/sso.aspx重定向回来的,分析一下ssoauth参数如果需要就创建认证cookie.
路子很简单,但是又两个地方要注意死循环:
HttpCookie c = HttpContext.Current.Request.Cookies[".FooAuth"];
if (c != null && c.HasKeys) // the cookie exists!
{
try
{
string cookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie);
return; // cookie decrypts successfully, continue processing the page
}
catch
{
}
}
// the authentication cookie doesn't exist - ask Bar.com if the user is logged in there
UriBuilder uri = new UriBuilder(Request.UrlReferrer);
if (uri.Host != "bar.com" || uri.Path != "/sso.aspx") // prevent infinite loop
{
Response.Redirect(http://bar.com/sso.aspx);
}
else
{
// we are here because the request we are processing is actually a response from bar.com
if (Request.QueryString["ssoauth"] == null)
{
// Bar.com also didn't have the authentication cookie
return; // continue normally, this user is not logged-in
} else
{
// user is logged in to Bar.com and we got his name!
string userName = (string)Request.QueryString["ssoauth"];
// let's create a cookie with the same name
FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddYears(1), true, "");
HttpCookie cookie = new HttpCookie(".FooAuth");
cookie.Value = FormsAuthentication.Encrypt(fat);
cookie.Expires = fat.Expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
}
}
同样的代码两个站点都要有,确保你使用了正确的cookie名字(.FooAuth vs. .BarAuth) . 因为cookie并不是真正意义上的共享,因为Web应用程序的有不同的<machineKey>配置节. 这里没有必要统一加密和解密的Key.
有些人把在url里面把用户名当作参数传递视为畏途.实际上有两件事情可以做来保护:首先我们可以检查引用页参数不接受bar.com/sso.aspx (or foo.com/ssp.aspx)以外的站点.其次,用户名可以可以通过相同的Key做一下加密.如果Foo和Bar使用不同的认证机制,额外的用户信息(比如email地址)同样也可以传递过去.