Single Sign On

转载 2007年09月24日 11:19:00

搜集了一些有关ASP.NET 2.0 Membership和单点登录的文章,都是英文的。

ASP.NET 2.0: Implementing Single Sign On (SSO) with Membership API (*新窗口打开)
写得比较简单,但提供了基本思路。尤其是能和membership结合起来,的确是我之所需。

Single Sign-On for everyone (*新窗口打开)
作者给出了6种场景下的SSO实现思路:1 虚拟目录中父-子应用程序之间,2 使用不同的凭据(用户名映射),3 同一域下两个子域之间,4 不同.NET版本下的应用程序之间,5 不同域中的两个应用程序之间,6 混合验证模式(Forms和Windows)。

ASP.NET 2.0 Membership, Roles, Forms Authentication, and Security Resources (*新窗口打开)
作者给出了大量与Membership相关的的资源,包括文档、书籍、博客等。  

 

=================================

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

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

<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> 


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


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

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

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


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

<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并不理解其中包含的用户名。

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

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应用的上下文中找到的用户名对其进行了填充。然后调用了RedirectFromLoginPageFoo应用创建了正确的验证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"); 

 

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 
= ".foo.com";  // 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 
= ".foo.com";  // Highlight 

HttpContext.Current.Response.Cookies.Add(cookie); 


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

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


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


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


<machineKey validationKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902" decryptionKey="F9D1A2D3E1D3E2F7B3D9F90FF3965ABDAC304902F8D923AC" validation="SHA1" decryption="3DES" />

 

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

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


  为了实现这一目的,我们分别在两个
Web站点中都创建一个特殊的页面(我们称之为sso.aspx)。这个页面的目的就是检查其域中是否存在Cookie,并返回登录的用户名,这样其他应用可以在对应的域中创建类似的Cookie。下面是来自Bar.comsso.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请求处理流水线中插入一些代码。可以在Application_BeginRequest事件中或者在一个自定义的HttpHandlerHttpModule中。其用意在于在所有的页面请求的尽可能早的地方检验验证Cookie是否存在:

1)
如果Foo.com中存在验证Cookie,继续处理请求。此时用户已登录Foo.com

2) 如果验证Cookie不存在,重定向到Bar.com/sso.aspx
3) 如果当前请求从Bar.com/sso.aspx重定向回来,分析ssoauth参数并在必要时创建验证Cookie

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


// 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 != "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,所以应用程序可以具有不同的<machineKey>元素。无需同步加密和验证密钥。

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


6.
混合模式验证(FormsWindows)中的SSO


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

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验证失败后执行下面的代码:


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错误(拒绝访问)。

小结


  我们讨论了在两个
ASP.NET应用之间进行的各种场景的单点登录。当然也可以实现不同平台间的异构系统上的SSO。其思路是同样的,但实现起来可能需要一些创造性的想法。

SSO(Single Sign On)系列(一)--SSO简介

不论什么类型的网站,到达一定规模之后一定会存在这样的问题:比如我们有N个系统,传统方式下我们就需要有N对不同的用户名和密码,本来这些系统的开发都能为我们带来良好的效益,用户在用的时候并不方便,每次都需...
  • liutengteng130
  • liutengteng130
  • 2015年07月09日 09:24
  • 2992

源代码解读Cas实现单点登出(single sign out)功能实现原理

原文地址:http://www.blogjava.net/xmatthew/archive/2008/07/09/213808.html 关于Cas实现单点登入(single sing on)功...
  • ynwso
  • ynwso
  • 2015年08月11日 10:59
  • 872

SSO(Single Sign On)系列(三)--CAS单点登录

上篇文章介绍了SSO的原理以及5种基本流程,相信看完了之后不难理解单点登录,而CAS是SSO的一种实现方案,原理是一样的。下面介绍一下。   CAS Server:负责完成对用户的认证工作,需要独立部...
  • liutengteng130
  • liutengteng130
  • 2015年07月14日 15:53
  • 4464

单点登录(Single Sign On)

单点登录(Single Sign On)     单点登录(SSO)的技术被越来越广泛地运用到各个领域的软件系统当中。本文从业务的角度分析了单点登录的需求和应用领域;从技术本身的角度分析了...
  • han2529386161
  • han2529386161
  • 2016年08月21日 15:04
  • 654

SSO-单点登录(single sign on)

SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。实现机...
  • gaoce227
  • gaoce227
  • 2017年05月27日 22:07
  • 75

单点登录SSO(Single Sign On)

单点登录是一次登录 ,可以访问多个应用 .在复杂的软件体系中 ,减少登录的操作时间 .Cookie登陆后 ,将登录成功的凭证(账号密码/Ticket/Token)放在Cookie(公共域名或者父域名下...
  • u011858405
  • u011858405
  • 2017年02月06日 11:01
  • 243

SSO单点登录(single sign in)

一、什么是单点登录SSO(Single Sign-On)   SSO是一种统一认证和授权机制,指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其...
  • u014633144
  • u014633144
  • 2015年04月29日 11:31
  • 314

SSO(Single Sign On)系列(二)--SSO原理

一、SSO整体访问流程   a.访问服务:SSO客户端发送请求访问应用系统提供服务资源 b.定向认证:SSO客户端会重定向用户请求到SSO服务器 c.用户认证:用户身份认证 d.发放票据:SSO服...
  • liutengteng130
  • liutengteng130
  • 2015年07月14日 15:52
  • 3578

jasig cas单点登录配置笔记之五

以上配置完成后还有一点问题,就是cas client的配置完成后,登录A应用,然后登录B应用,需要重新认证. 仔细阅读文档,发现原来jasig Cas不能支持非SSL方式的统一登录.实际上登录首页...
  • rishengcsdn
  • rishengcsdn
  • 2013年09月24日 17:32
  • 7265

VMware vSphere Web Services SDK编程指南(五)- 5.1 客户端应用(vCenter 服务器连接)

5.1 vCenter 服务器连接 5.2 与 vCenter 服务器建立一个单点登录会话 每个 vCenter 服务器客户端应用程序都必须连接到服务器,并将用户帐户凭证传递给服务器认证,建立连接后...
  • zhouxukun123
  • zhouxukun123
  • 2017年07月31日 23:32
  • 409
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Single Sign On
举报原因:
原因补充:

(最多只允许输入30个字)