Single Sign On

摘要:
翻译:SingleSign OnforEveryone原始地址:http://bbs.hidotnet.com/22656/ShowPost.aspx单点登录是最近的热门话题。调用RedirectFromLoginPage时,将创建一个cookie,其中包含带有登录用户名的加密FormsAuthenticationTicket。如果Foo和Bar中的两个属性匹配,它们可以在相同的保护级别使用相同的cookie,这也实现了SSO:当保护属性设置为“All”时,cookie将同时加密和验证。默认的身份验证和加密密钥存储在Machine.Config中,可以在Web中重写。应用程序的配置。

前一阵写了一篇Blog,给出了一些SSO的资料(http://www.cnblogs.com/AndersLiu/archive/2007/05/25/760041.html)。现在把其中的一篇翻译出来。

翻译:Single Sign-On for Everyone
原文地址:http://bbs.hidotnet.com/22656/ShowPost.aspx

单点登录(Single Sign-On,SSO)是这些天的热点话题。我的很多客户都有多个Web应用,运行在不同子域的不同.NET Framework版本中,甚至是不同的域中。他们都希望用户能够只登录一次,就能在各个不同的Web站点中保持登录状态。今天我们来一起看看如何在各种不同的场景中实现SSO。我们首先从最简单的情况开始,然后逐步构建它:

1. 虚拟子目录中的父、子应用之间的SSO
2. 使用不同授权凭证(用户名映射)的SSO
3. 同一域下的两个子域中的Web应用之间的SSO
4. 不同.NET版本下的应用之间的SSO
5. 不同域之众的两个应用之间的SSO
6. 混合模式验证(Forms和Windows)中的SSO

1. 虚拟子目录中的父、子应用之间的SSO

  假设有两个.NET应用——FooBar,并且Bar位于Foo的一个虚拟子目录中(http://foo.com/bar)。两个应用都实现了Forms验证。实现Forms验证需要重写Application_AuthenticateRequest,在这里进行验证,并在验证成功后调用FormsAuthentication.RedirectFromLoginPage,将登录的用户名(或系统中用于标识用户的其他信息)作为参数传递进去。在ASP.NET中,登录用户状态通过保存在客户端Cookie中进行持久化。当调用RedirectFromLoginPage时,就会创建一个Cookie,其中包含了加密的、带有登录用户名的FormsAuthenticationTicketWeb.Config中有一节用于定义如何创建该Cookie

Single Sign On第1张<authentication mode="Forms"> 
Single Sign On第1张
Single Sign On第1张    
<forms name=".FooAuth" protection="All" timeout="60" loginUrl="login.aspx" /> 
Single Sign On第1张
Single Sign On第1张
</authentication> 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张
<authentication mode="Forms"> 
Single Sign On第1张
Single Sign On第1张    
<forms name=".BarAuth" protection="All" timeout="60" loginUrl="login.aspx" /> 
Single Sign On第1张
Single Sign On第1张
</authentication> 

  这里最重要的两个属性是nameprotection。如果在FooBar中,这两个属性是匹配的,那么它们就能在同样的保护级别上使用相同的Cookie,也就实现了SSO

Single Sign On第1张<authentication mode="Forms"> 
Single Sign On第1张
Single Sign On第1张    
<forms name=".SSOAuth" protection="All" timeout="60" loginUrl="login.aspx" /> 
Single Sign On第1张
Single Sign On第1张
</authentication>

  当将protection属性设置为“All”以后,会同时对Cookie进行加密盒验证(通过散列值)。默认的验证和加密密钥存储在Machine.Config中,并且可以在应用程序的Web.Config中重写。其默认值为:

Single Sign On第1张<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey=" AutoGenerate,IsolateApps" validation="SHA1" />

  IsolateApps意味着将为每个应用程序都生成一个不同的密钥。我们不能这样做。为了在所有应用程序中都能加密/解谜Cookie,需要移除IsolateApps属性,并为使用SSO的所有应用程序指定相同的具体密钥:

Single Sign On第1张<machineKey validationKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902" decryptionKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902F8D923AC" validation="SHA1" />

  如果你正在针对不同的用户存储进行验证,这就是所有需要做的——对Web.Config的一点修改。

2. 使用不同授权凭证(用户名映射)的SSO

  但是,如果Foo应用使用其自己的数据库,而Bar应用程序使用Membership API或其他形式的验证呢?在这种情况下,为Foo创建的Cookie并不适用于Bar,因为Bar并不理解其中包含的用户名。

  为了使其工作,需要创建第二个验证Cookie,专门用于Bar应用。还需要一种方式来将Foo用户映射到Bar用户。假设Foo应用中登录了一个“John Doe”用户,并且经过检测发现这个用户在Bar应用中的标识是“johnd”。在Foo的验证方法中需要添加下面的代码:

Single Sign On第1张FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1"johnd", DateTime.Now, DateTime.Now.AddYears(1), true""); 
Single Sign On第1张
Single Sign On第1张HttpCookie cookie 
= new HttpCookie(".BarAuth"); 
Single Sign On第1张
Single Sign On第1张cookie.Value 
= FormsAuthentication.Encrypt(fat); 
Single Sign On第1张
Single Sign On第1张cookie.Expires 
= fat.Expiration; 
Single Sign On第1张
Single Sign On第1张HttpContext.Current.Response.Cookies.Add(cookie); 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张FormsAuthentication.RedirectFromLoginPage(
"John Doe"); 

  硬编码的用户名仅仅用于演示目的。这段代码为Bar应用创建了FormsAuthenticationTicket,并用从Bar应用的上下文中找到的用户名对其进行了填充。然后调用了RedirectFromLoginPageFoo应用创建了正确的验证Cookie。如果你将两个应用程序的验证Cookie名字改成了相同的(见前面的示例),那么要注意现在他们是不同的了,我们无需再为每个站点使用相同的Cookie了:

Single Sign On第1张<authentication mode="Forms"> 
Single Sign On第1张
Single Sign On第1张    
<forms name=".FooAuth" protection="All" timeout="60" loginUrl="login.aspx" slidingExpiration="true"/> 
Single Sign On第1张
Single Sign On第1张
</authentication> 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张
<authentication mode="Forms"> 
Single Sign On第1张
Single Sign On第1张    
<forms name=".BarAuth" protection="All" timeout="60" loginUrl="login.aspx" slidingExpiration="true"/> 
Single Sign On第1张
Single Sign On第1张
</authentication> 

  现在,只要用户登录到Foo,他就会被映射到Bar用户,并在会随着Foo验证票据创建一个Bar验证票据。如果希望相反的方向也能工作,只需在Bar应用中添加类似的代码即可:

Single Sign On第1张FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1"John Doe", DateTime.Now, DateTime.Now.AddYears(1), true""); 
Single Sign On第1张HttpCookie cookie 
= new HttpCookie(".FooAuth"); 
Single Sign On第1张cookie.Value 
= FormsAuthentication.Encrypt(fat); 
Single Sign On第1张cookie.Expires 
= fat.Expiration; 
Single Sign On第1张HttpContext.Current.Response.Cookies.Add(cookie); 
Single Sign On第1张FormsAuthentication.RedirectFromLoginPage(
"johnd"); 

  但仍然要确保Web.Config中的<machineKey>元素中为两个应用提供了匹配的验证和加密密钥。

3. 同一域下的两个子域中的Web应用之间的SSO

  现在假设FooBar配置为在不同的域http://foo.comhttp://bar.foo.com中运行。前面的代码都不能使用了,因为Cookies将被存放到不同的文件中,并且应用程序彼此看不到(对方的Cookie)。为了使其能够工作,我们需要创建域级别的Cookies,并使其对所有子域可见。这样我们就不能使用RedirectFromLoginPage方法了,因为它不适合创建域级别的Cookie。我们可以手动完成这一工作:

Single Sign On第1张FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1"johnd", DateTime.Now, DateTime.Now.AddYears(1), true""); 
Single Sign On第1张
Single Sign On第1张HttpCookie cookie 
= new HttpCookie(".BarAuth"); 
Single Sign On第1张
Single Sign On第1张cookie.Value 
= FormsAuthentication.Encrypt(fat); 
Single Sign On第1张
Single Sign On第1张cookie.Expires 
= fat.Expiration; 
Single Sign On第1张
Single Sign On第1张cookie.Domain 
= ".foo.com";  // Highlight 
Single Sign On第1张

Single Sign On第1张HttpContext.Current.Response.Cookies.Add(cookie); 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张FormsAuthenticationTicket fat 
= new FormsAuthenticationTicket(1"John Doe", DateTime.Now, DateTime.Now.AddYears(1), true""); 
Single Sign On第1张
Single Sign On第1张HttpCookie cookie 
= new HttpCookie(".FooAuth"); 
Single Sign On第1张
Single Sign On第1张cookie.Value 
= FormsAuthentication.Encrypt(fat); 
Single Sign On第1张
Single Sign On第1张cookie.Expires 
= fat.Expiration; 
Single Sign On第1张
Single Sign On第1张cookie.Domain 
= ".foo.com";  // Highlight 
Single Sign On第1张

Single Sign On第1张HttpContext.Current.Response.Cookies.Add(cookie); 
Single Sign On第1张
Single Sign On第1张

  注意高亮显示的行(Anders Liu:为了避免格式问题,我使用的是注释“// Highlight”)。通过明确地将Cookie的域设定为“.foo.com”,可以确保在http://foo.comhttp://bar.foo.com以及其他子域中都能看到该Cookie。你也可以将Bar的验证Cookie域设置为“bar.foo.com”。这样更加安全,因为其他子域看不到它。注意RFC 2109在Cookie域值中要求两个periods,因此我们在前面添加了一个period——“.foo.com”。

  另外,确保在每个应用的Web.Config中使用相同的<machineKey>元素。只有一种特殊情况,接下来的小节将探讨这一情况。

4.
不同.NET版本下的应用之间的SSO

  有一种可能是Foo和Bar应用运行在不同版本的.NET中。这是前面的例子就不能工作了。这是因为ASP.NET 2.0使用了不同的加密方法对验证票据进行加密。ASP.NET 1.1使用的是3DES,而ASP.NET 2.0使用的是AES。幸运的是,ASP.NET 2.0为了向后兼容,提供了一个新的属性:

Single Sign On第1张<machineKey validationKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902" decryptionKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902F8D923AC" validation="SHA1" decryption="3DES" />

  设置decryption="3DES"可以让ASP.NET 2.0使用老的加密方法,这样Cookies就又匹配了。不要向ASP.NET 1.1Web.Config中添加这个属性,否则会导致错误。

5. 不同域之众的两个应用之间的SSO

  至此为止我们成功地创建了共享的验证Cookie,但如果Foo和Bar位于不同的域——http://foo.com和http://bar.com——中呢?它们不可能共享Cookie,也不能彼此创建第二Cookie。这种情况下,每个站点需要创建自己的Cookies,并调用其他站点来验证用户是否已经在别处登录了。完成这一工作的一种方法就是通过一些列的重定向。

  为了实现这一目的,我们分别在两个Web站点中都创建一个特殊的页面(我们称之为sso.aspx)。这个页面的目的就是检查其域中是否存在Cookie,并返回登录的用户名,这样其他应用可以在对应的域中创建类似的Cookie。下面是来自Bar.com的sso.aspx:

Single Sign On第77张Single Sign On第78张<%@ Page Language="C#" %> 
Single Sign On第1张
Single Sign On第1张
Single Sign On第77张Single Sign On第78张
<script language="C#" runat="server"> 
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张
void Page_Load() 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张

Single Sign On第83张
Single Sign On第83张    
// this is our caller, we will need to redirect back to it eventually 
Single Sign On第83张

Single Sign On第83张    UriBuilder uri 
= new UriBuilder(Request.UrlReferrer); 
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张    HttpCookie c 
= HttpContext.Current.Request.Cookies[".BarAuth"]; 
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张    
if (c != null && c.HasKeys) // the cookie exists! 
Single Sign On第83张

Single Sign On第88张Single Sign On第89张    

Single Sign On第83张
Single Sign On第83张        
try 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张        

Single Sign On第83张
Single Sign On第83张            string cookie 
= HttpContext.Current.Server.UrlDecode(c.Value); 
Single Sign On第83张
Single Sign On第83张            FormsAuthenticationTicket fat 
= FormsAuthentication.Decrypt(cookie);         
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张            uri.Query 
= uri.Query + "&ssoauth=" + fat.Name; // add logged-in user name to the query 
Single Sign On第83张

Single Sign On第116张        }
 
Single Sign On第83张
Single Sign On第83张        
catch 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张        

Single Sign On第83张
Single Sign On第116张        }
 
Single Sign On第83张
Single Sign On第116张    }
 
Single Sign On第83张
Single Sign On第83张    Response.Redirect(uri.ToString()); 
// redirect back to the caller 
Single Sign On第83张

Single Sign On第116张}
 
Single Sign On第83张
Single Sign On第131张
Single Sign On第1张
</script> 
Single Sign On第1张
Single Sign On第1张

  这个页面总是会重定向回调用方。如果Bar.com中存在验证Cookie,会解密用户名并通过查询字符串中的ssoauth参数返回。

  在另外一端(Foo.com),我们需要像http请求处理流水线中插入一些代码。可以在Application_BeginRequest事件中或者在一个自定义的HttpHandler或HttpModule中。其用意在于在所有的页面请求的尽可能早的地方检验验证Cookie是否存在:

1) 如果Foo.com中存在验证Cookie,继续处理请求。此时用户已登录Foo.com
2) 如果验证Cookie不存在,重定向到Bar.com/sso.aspx
3) 如果当前请求从Bar.com/sso.aspx重定向回来,分析ssoauth参数并在必要时创建验证Cookie。

  这看起来相当简单,但要注意无限循环:

Single Sign On第1张// see if the user is logged in 
Single Sign On第1张

Single Sign On第1张HttpCookie c 
= HttpContext.Current.Request.Cookies[".FooAuth"]; 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张
if (c != null && c.HasKeys) // the cookie exists! 
Single Sign On第1张

Single Sign On第77张Single Sign On第78张

Single Sign On第83张
Single Sign On第83张    
try 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张    

Single Sign On第83张
Single Sign On第83张        
string cookie = HttpContext.Current.Server.UrlDecode(c.Value); 
Single Sign On第83张
Single Sign On第83张        FormsAuthenticationTicket fat 
= FormsAuthentication.Decrypt(cookie); 
Single Sign On第83张
Single Sign On第83张        
return// cookie decrypts successfully, continue processing the page 
Single Sign On第83张

Single Sign On第116张    }
 
Single Sign On第83张
Single Sign On第83张    
catch 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张    

Single Sign On第83张
Single Sign On第116张    }
 
Single Sign On第83张
Single Sign On第131张}
 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张
// the authentication cookie doesn't exist - ask Bar.com if the user is logged in there 
Single Sign On第1张

Single Sign On第1张UriBuilder uri 
= new UriBuilder(Request.UrlReferrer); 
Single Sign On第1张
Single Sign On第1张
Single Sign On第1张
if (uri.Host != "bar.com" || uri.Path != "/sso.aspx"// prevent infinite loop 
Single Sign On第1张

Single Sign On第77张Single Sign On第78张

Single Sign On第83张
Single Sign On第83张    Response.Redirect(http:
//bar.com/sso.aspx); 
Single Sign On第83张

Single Sign On第131张}
 
Single Sign On第1张
Single Sign On第1张
else 
Single Sign On第1张
Single Sign On第77张Single Sign On第78张

Single Sign On第83张
Single Sign On第83张    
// we are here because the request we are processing is actually a response from bar.com 
Single Sign On第83张

Single Sign On第83张
Single Sign On第83张    
if (Request.QueryString["ssoauth"== null
Single Sign On第83张
Single Sign On第88张Single Sign On第89张    

Single Sign On第83张
Single Sign On第83张        
// Bar.com also didn't have the authentication cookie 
Single Sign On第83张

Single Sign On第83张        
return// continue normally, this user is not logged-in 
Single Sign On第83张

Single Sign On第116张    }
 else 
Single Sign On第83张
Single Sign On第88张Single Sign On第89张    

Single Sign On第83张
Single Sign On第83张
Single Sign On第83张        
// user is logged in to Bar.com and we got his name! 
Single Sign On第83张

Single Sign On第83张        
string userName = (string)Request.QueryString["ssoauth"]; 
Single Sign On第83张
Single Sign On第83张
Single Sign On第83张        
// let's create a cookie with the same name 
Single Sign On第83张

Single Sign On第83张        FormsAuthenticationTicket fat 
= new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddYears(1), true""); 
Single Sign On第83张
Single Sign On第83张        HttpCookie cookie 
= new HttpCookie(".FooAuth"); 
Single Sign On第83张
Single Sign On第83张        cookie.Value 
= FormsAuthentication.Encrypt(fat); 
Single Sign On第83张
Single Sign On第83张        cookie.Expires 
= fat.Expiration; 
Single Sign On第83张
Single Sign On第83张        HttpContext.Current.Response.Cookies.Add(cookie); 
Single Sign On第83张
Single Sign On第116张    }
 
Single Sign On第83张
Single Sign On第131张}
 
Single Sign On第1张
Single Sign On第1张


  两个站点都同样需要这段代码,但要在每个站点中使用正确的Cookie名字(.FooAuth vs. .BarAuth)。由于实际上并没有共享Cookie,所以应用程序可以具有不同的<machineKey>元素。无需同步加密和验证密钥。

  很多人可能比较担心在查询字符串中传递用户名所带来的安全隐患。很多方法可以对其进行保护。首先,要检查引用方,不接受来自任何源的ssoauth参数,但除了bar.com/sso.asp(或foo.com/sso.aspx)。其次,可以很容易地使用共享密钥对用户名进行加密。如果Foo和Bar使用了不同的验证机制,也可以用类似的方式传递用户的附加信息(例如email地址)。

6.
混合模式验证(Forms和Windows)中的SSO

  到现在为止,我们一直在处理Forms验证的情况。但如果我们希望对于Internet用户首先采用Forms验证,如果验证失败,再检查是否是NT域中的Intranet用户并进行验证。理论上,我们可以通过下面的参数来检查是否与请求关联了一个Windows已登录用户:

Single Sign On第1张Request.ServerVariables["LOGON_USER"


  然而,除非站点禁用了匿名访问,否则该值一直为空。我们可以在IIS控制面板中禁用匿名访问,并启用集成Windows验证。这样LOGON_USER值中将包含已登录的Intranet用户的NT域名。但是所有的Internet用户将面临Windows用户名和密码的挑战。这不爽。我们希望Internet用户可以通过Forms验证进行登录,而当失败的时候再检测其Windows域凭证。

  解决这一问题的一个方法是,为Intranet用户提供一个特殊的入口页,在这里启用集成Windows验证,验证域用户,然后创建一个Forms Cookie并导航到主站点。我们甚至可以通过Server.Transfer来隐藏Intranet用户访问了不同的页面这一事实。

  还有一种简单的解决方案。因为IIS处理验证过程,如果一个Web站点启用了匿名访问,IIS会将请求正确地传递给ASP.NET 运行时。它不会尝试执行任何类型的验证。然而,如果请求的结果是一个验证错误(401),IIS会尝试特定于该站点的另外一种验证方法。你可以同时启用匿名访问和集成Windows验证,然后再Forms验证失败后执行下面的代码:

Single Sign On第77张Single Sign On第78张if (System.Web.HttpContext.Current.Request.ServerVariables["LOGON_USER"== ""
Single Sign On第83张
Single Sign On第83张    System.Web.HttpContext.Current.Response.StatusCode 
= 401
Single Sign On第83张
Single Sign On第83张    System.Web.HttpContext.Current.Response.End(); 
Single Sign On第83张
Single Sign On第131张}
 
Single Sign On第1张
Single Sign On第1张
else 
Single Sign On第1张
Single Sign On第77张Single Sign On第78张

Single Sign On第83张
Single Sign On第83张    
// Request.ServerVariables["LOGON_USER"] has a valid domain user now! 
Single Sign On第83张

Single Sign On第131张}
 
Single Sign On第1张
Single Sign On第1张

  这段代码执行时,会首先检测域用户并得到一个空的字符串。然后它会终止当前请求并向IIS返回验证错误(401)。这将导致IIS使用另外一种验证机制,在这种情况下是集成Windows验证。如果用户已经登录到域,请求会被重复一次,此时会填充NT域用户信息。如果用户没有登录到域,他将有三次机会输入Windows用户名/密码。如果用户无法在三次尝试之内完成

转自:http://www.cnblogs.com/AndersLiu/archive/2007/06/20/790894.html

免责声明:文章转载自《Single Sign On》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇FormData、Blob、File、ArrayBuffer数据类型解决delphi使用sqlite时中文最后一个字是乱码的问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

selenium cookie登录

前言 爬虫方向的小伙伴们都知道网页爬虫经常遇到的问题就是登录账户,有些简单的网站我们可以简单的send key来输入账户密码就可以登录,但是有很多网站需要验证码之类的就不太好用了,这时候就体现到了cookie登录的优点了,前段时间网上搜了搜,发现没有什么完整的教程,本文就使用selenium来cookie登录为例做一个简单的教程。 环境准备 python...

ASP.NET Core2.1 你不得不了解的GDPR(Cookie处理)

前言 时间一晃ASP.NET Core已经迭代到2.1版本了. 迫不及待的的下载了最新的版本,然后生成了一个模版项目来试试水. ...然后就碰到问题了... 我发现..cookie竟然存不进去了..(怨念+100) 找了各种资料,无果 最后无奈只得麻烦善友老师..老师回了一句GDPR 虽然一头雾水,但是还是去百度了一发.终于找到原因..(E文好的可以自...

Django之Cookie

cookie是什么? 保存在浏览器端“键值对” 服务端可以向用户浏览器端写cookie 客户端每次发请求时,会携带cookie去 应用场景: 投票 用户登录 1、获取Cookie: request.COOKIES['key'] request.get_signed_cookie(key, default=RAISE_ERROR, salt='',...

jQuery 操作Cookie

转自:http://www.cnblogs.com/luluping/archive/2009/04/23/1442465.html jQuery.cookie = function(name, value, options) {          if (typeof value != 'undefined') {                 ...

HTTP协议详解(深入理解)

版权声明:本文为CSDN博主「有抱负的小狮子」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_38087538/article/details/82838762 引入 超文本传输协议(HTTP,HyperText Transfer Protocol...

嵌入式 Linux下编译并使用curl静态库

#x86./configure --disable-shared --enable-static --disable-ftp --disable-ipv6 --disable-rtsp --disable-tftp --disable-telnet --disable-largefile --disable-smtp --disable-imap --wi...