前一阵写了一篇Blog,给出了一些SSO的资料(http://www.cnblogs.com/AndersLiu/archive/2007/05/25/760041.html)。现在把其中的一篇翻译出来。 翻译:Single Sign-On for Everyone <authentication mode="Forms">
<forms name=".FooAuth" protection="All" timeout="60" loginUrl="login.aspx" /> </authentication> <authentication mode="Forms"> <forms name=".BarAuth" protection="All" timeout="60" loginUrl="login.aspx" /> </authentication> 这里最重要的两个属性是name和protection。如果在Foo和Bar中,这两个属性是匹配的,那么它们就能在同样的保护级别上使用相同的Cookie,也就实现了SSO: <authentication mode="Forms">
<forms name=".SSOAuth" protection="All" timeout="60" loginUrl="login.aspx" /> </authentication> 当将protection属性设置为“All”以后,会同时对Cookie进行加密盒验证(通过散列值)。默认的验证和加密密钥存储在Machine.Config中,并且可以在应用程序的Web.Config中重写。其默认值为: <machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey=" AutoGenerate,IsolateApps" validation="SHA1" />
IsolateApps意味着将为每个应用程序都生成一个不同的密钥。我们不能这样做。为了在所有应用程序中都能加密/解谜Cookie,需要移除IsolateApps属性,并为使用SSO的所有应用程序指定相同的具体密钥: <machineKey validationKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902" decryptionKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902F8D923AC" validation="SHA1" />
如果你正在针对不同的用户存储进行验证,这就是所有需要做的——对Web.Config的一点修改。 2. 使用不同授权凭证(用户名映射)的SSO 但是,如果Foo应用使用其自己的数据库,而Bar应用程序使用Membership API或其他形式的验证呢?在这种情况下,为Foo创建的Cookie并不适用于Bar,因为Bar并不理解其中包含的用户名。 FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, "johnd", DateTime.Now, DateTime.Now.AddYears(1), true, "");
HttpCookie cookie = new HttpCookie(".BarAuth"); cookie.Value = FormsAuthentication.Encrypt(fat); cookie.Expires = fat.Expiration; HttpContext.Current.Response.Cookies.Add(cookie); FormsAuthentication.RedirectFromLoginPage("John Doe"); 硬编码的用户名仅仅用于演示目的。这段代码为Bar应用创建了FormsAuthenticationTicket,并用从Bar应用的上下文中找到的用户名对其进行了填充。然后调用了RedirectFromLoginPage为Foo应用创建了正确的验证Cookie。如果你将两个应用程序的验证Cookie名字改成了相同的(见前面的示例),那么要注意现在他们是不同的了,我们无需再为每个站点使用相同的Cookie了: <authentication mode="Forms">
<forms name=".FooAuth" protection="All" timeout="60" loginUrl="login.aspx" slidingExpiration="true"/> </authentication> <authentication mode="Forms"> <forms name=".BarAuth" protection="All" timeout="60" loginUrl="login.aspx" slidingExpiration="true"/> </authentication> 现在,只要用户登录到Foo,他就会被映射到Bar用户,并在会随着Foo验证票据创建一个Bar验证票据。如果希望相反的方向也能工作,只需在Bar应用中添加类似的代码即可: FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, "John Doe", 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); FormsAuthentication.RedirectFromLoginPage("johnd"); 但仍然要确保Web.Config中的<machineKey>元素中为两个应用提供了匹配的验证和加密密钥。 3. 同一域下的两个子域中的Web应用之间的SSO 现在假设Foo和Bar配置为在不同的域http://和http://bar.中运行。前面的代码都不能使用了,因为Cookies将被存放到不同的文件中,并且应用程序彼此看不到(对方的Cookie)。为了使其能够工作,我们需要创建域级别的Cookies,并使其对所有子域可见。这样我们就不能使用RedirectFromLoginPage方法了,因为它不适合创建域级别的Cookie。我们可以手动完成这一工作: FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, "johnd", DateTime.Now, DateTime.Now.AddYears(1), true, "");
HttpCookie cookie = new HttpCookie(".BarAuth"); cookie.Value = FormsAuthentication.Encrypt(fat); cookie.Expires = fat.Expiration; cookie.Domain = "."; // Highlight HttpContext.Current.Response.Cookies.Add(cookie); FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, "John Doe", DateTime.Now, DateTime.Now.AddYears(1), true, ""); HttpCookie cookie = new HttpCookie(".FooAuth"); cookie.Value = FormsAuthentication.Encrypt(fat); cookie.Expires = fat.Expiration; cookie.Domain = "."; // Highlight HttpContext.Current.Response.Cookies.Add(cookie); 注意高亮显示的行(Anders Liu:为了避免格式问题,我使用的是注释“// Highlight”)。通过明确地将Cookie的域设定为“.”,可以确保在http://和http://bar.以及其他子域中都能看到该Cookie。你也可以将Bar的验证Cookie域设置为“bar.”。这样更加安全,因为其他子域看不到它。注意RFC 2109在Cookie域值中要求两个periods,因此我们在前面添加了一个period——“.”。 <machineKey validationKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902" decryptionKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902F8D923AC" validation="SHA1" decryption="3DES" />
设置decryption="3DES"可以让ASP.NET 2.0使用老的加密方法,这样Cookies就又匹配了。不要向ASP.NET 1.1的Web.Config中添加这个属性,否则会导致错误。 5. 不同域之众的两个应用之间的SSO <%@ 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参数返回。 // see if the user is logged in
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 != "" || uri.Path != "/sso.aspx") // prevent infinite loop { Response.Redirect(http:///sso.aspx); } else { // we are here because the request we are processing is actually a response from 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,所以应用程序可以具有不同的<machineKey>元素。无需同步加密和验证密钥。 Request.ServerVariables["LOGON_USER"]
if (System.Web.HttpContext.Current.Request.ServerVariables["LOGON_USER"] == "") {
System.Web.HttpContext.Current.Response.StatusCode = 401; System.Web.HttpContext.Current.Response.End(); } else { // Request.ServerVariables["LOGON_USER"] has a valid domain user now! } 这段代码执行时,会首先检测域用户并得到一个空的字符串。然后它会终止当前请求并向IIS返回验证错误(401)。这将导致IIS使用另外一种验证机制,在这种情况下是集成Windows验证。如果用户已经登录到域,请求会被重复一次,此时会填充NT域用户信息。如果用户没有登录到域,他将有三次机会输入Windows用户名/密码。如果用户无法在三次尝试之内完成登录,他会得到403错误(拒绝访问)。 |
|