`
sty2008boy
  • 浏览: 302453 次
  • 性别: Icon_minigender_1
  • 来自: 太原
社区版块
存档分类
最新评论

SSO解决方案大全(cookie跨域)

 
阅读更多
前段时间为我们的系统做SSO(单点登录)参考了很多资料,其中包括博客园二级域名的登录.翻译本文是由于作者的一句话:思想都是一样的,只不过实现起来需要创造性思维.

Single Sign-On (SSO)是近来的热门话题. 很多和我交往的客户中都有不止一个运行在.Net框架中的Web应用程序或者若干子域名.而他们甚至希望在不同的域名中也可以只登陆一次就可以畅游所有站点.今天我们关注的是如何在各种不同的应用场景中实现 SSO. 我们由简到繁,逐一攻破.

虚拟目录的主应用和子应用间实现SSO
使用不同验证机制实现SSO (username mapping)
同一域名中,子域名下的应用程序间实现SSO
运行在不同版本.NET下的应用程序间实现SSO
两个不同域名下的Web应用程序间实现SSO
混合身份验证方式模式 (Forms and Windows)下实现SSO

1. 虚拟目录的主应用和子应用之间实现SSO

假设有两个.Net的Web应用程序-Foo和Bar,Bar运行在Foo虚拟目录的子目录(http://foo.com/bar).二者都实现了Forms认证.实现Forms认证需要我们重写Application_AuthenticateRequest,在这个时机我们完成认证一旦通过验证就调用一下FormsAuthentication.RedirectFromLoginPage.这个方法接收的参数是用户名或者其它的一些身份信息.在Asp.net中登录用户的状态是持久化存储在客户端的cookie中.当你调用RedirectFromLoginPage时就会创建一个包含加密令牌FormsAuthenticationTicket的cookie,cookie名就是登录用户的用户名.下面的配置节在Web.config定义了这种cookie如何创建:

比较重要的两个属性是 name 和protection. 按照下面的配置就可以让Foo和Bar两个程序在同样的保护级别下读写Cookie,这就实现了SSO的效果:

<authentication mode="Forms">
<forms name=".SSOAuth" protection="All" timeout="60" loginUrl="login.aspx" />
</authentication>

 

当 protection属性设置为 "All",通过Hash值进行加密和验证数据都存放在Cookie中.默认的验证和加密使用的Key都存储在machine.config文件,我们可以在应用程序的Web.Config文件覆盖这些值.默认值如下:

<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey=" AutoGenerate,IsolateApps" validation="SHA1" />

 

IsolateApps表示为每个应用程序生成不同的Key.我们不能使用这个.为了能在多个应用程序中使用相同的Key来加密解密cookie,我们可以移除IsolateApps 选项或者更好的方法是在所有需要实现SSO的应用程序的Web.Config中设置一个具体的Key值:  

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

如果你使用同样的存储方式,实现SSO只是改动一下Web.config而已.

2.使用不同认证机制实现SSO (username mapping)

要是FOO站点使用database来做认证,Bar站点使用Membership API或者其它方式做认证呢?这种情景中FOO站点创建的cookie对Bar站点毫无用处,因为cookie中的用户名对Bar没有什么意义.

要想cookie起作用,你就需要再为Bar站点创建一个认证所需的cookie.这里你需要为两个站点的用户做一下映射.假如有一个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站点的上下文中就是有意义的了. 这时再调用 RedirectFromLoginPage创建正确的认证cookie.上面的例子你统一了了Forms 认证的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站点登录在Foo站点通行,那么代码就会是这样:

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>配置节有相同的加密和解密的Key!

 

3. 同一域名中,各子域名下应用程序间实现SSO

要是这样的情况又将如何:Foo Bar两个站点运行在不同的域名下: http://foo.com and http://bar.foo.com. 上面的代码又不起作用了:因为cookie会存储在不同的文件中,各自的cookie对其它网站不可见.为了能让它起作用我们需要创建域级cookie,因为域级cookie对子域名都是可见的!这里我们也不能再使用 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 = ".foo.com";
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";
HttpContext.Current.Response.Cookies.Add(cookie);

注意cookie.Domain = ".foo.com";注意这一行.这里明确指定了cookie的域名为".foo.com",这样我们就保证了cookie对 http://foo.com 和 http://bar.foo.com 以及其它子域名都是可见的.(译者注:cookie的域名匹配规则是从右到左) .你可以通过设置Bar站点的认证cookie的域名为"bar.foo.com".这样对于其它子域名的站点它的cookie也是不可见的,这样安全了.注意 RFC 2109 要求cookie前面有两个周期所以我们添加了一个过期时间.(cookie值实际上是一个字符串,各参数用逗号隔开).

再次提醒,这里还是需要统一一下各个站点的Web.config的<machineKey>配置节的Key值. 这种解决方案只有一种异常的情况,且看下节详解.

4. 运行在不同版本.Net下应用程序间实现SSO

要是Foo和Bar站点运行在不同的.Net环境中上面的例子都行不通.这是由于Asp.net 2.0使用了不同于1.1的加密算法:1.1版本使用的是3DES,2.0是AES.万幸,Asp.net2.0中有一个属性可以兼容1.1:

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

设置decryption="3DES"就会让 ASP.NET 2.0使用旧版本的加密算法使cookie能够正常使用.不要企图在Asp.net1.1的Web.config文件中添加这个属性,那会报错.

 

5. 两个不同域名下的应用程序实现SSO

我们已经成功的创建了可以共享的认证Cookie,但是如果Foo站点和Bar站点在不同域名下呢,例如: http://foo.com 和 http://bar.com? 他们不能共享cookie也不能为对方在创建一个可读的cookie.这种情况下每个站点需要创有各自的cookie,调用其它站点的页面来验证用户是否登录.其中一种实现方式就是使用一系列的重定向.

为了实现上述目标,我们需要在每个站点都创建一个特殊的页面(比如:sso.aspx).这个页面的作用就是来检查该域名下的cookie是否存在并返回已经登录用户的用户名.这样其它站点也可以为这个用户创建一个cookie了.下面是Bar.com的sso.aspx:

Bar.com:
<%@ 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.


路子很简单,但是又两个地方要注意死循环:

// 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并不是真正意义上的共享,因为Web应用程序的有不同的<machineKey>配置节. 这里没有必要统一加密和解密的Key.

有些人把在url里面把用户名当作参数传递视为畏途.实际上有两件事情可以做来保护:首先我们可以检查引用页参数不接受bar.com/sso.aspx (or foo.com/ssp.aspx)以外的站点.其次,用户名可以可以通过相同的Key做一下加密.如果Foo和Bar使用不同的认证机制,额外的用户信息(比如email地址)同样也可以传递过去.

 

6. 混合身份验证模式下 (Forms and Windows)实现SSO

Request.ServerVariables["LOGON_USER"]

上面我们都是处理的Forms认证.要是我们这样设计认证过程呢:先做Forms认证,如果没有通过就检查Intranet用户是否已经在NT域上登录过了.这个思路我们需要检查下面的参数来看和请求关联的Windows logo信息:

    但是除非我们的站点都是禁用匿名登录的,否则这个值总是空的.我们可以在IIS的控制面板禁用匿名登录并为我们的站点启用Windows集成认证.这样LOGON_USER 值就包含了NT域登录用户的名字.但是所有Internet用户的都会遇到用户名和密码的难题,这就不好了,我们要让Internet用户使用Forms认证要是这种方式失败了再使用Windows域认证.

这个问题的解决方法之一就是为Intranet用户设置一个特殊的入口页面:Windows集成认证方式可用,验证域用户,创建Forms cookie重定向到主站点.我们甚至可以隐藏这样一个事实:由于Server.TransferIntranet用户实际上访问了不同的页面.

也有一个简单的解决方法.这个方法的基础是IIS掌控认证处理.如果站点对匿名用户可用,IIS就把请求传递给Asp.net运行时.并试图进行认证要是失败了就引发一个401错误.IIS会试图寻找另外该站点的其它认证方式 .你要设置匿名访问和集成认证可用并在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!
}

 

   这段代码执行时,它会检查域用户并取得一个空的初始值.这回终止当前请求并返回认证的401错误到IIS.这就让IIS自动选择另外的认证机制,Windows集成认证方式就是候选方式.如果用户可以登录到域,请求就可以继续,并附加上了NT域用户的信息. 如果用户没有在域中登录会有三次输入用户名密码的机会.如果三次失败他就会得到一个403错误(AccessDenied).

结论

我们考查了在各种场景中在两个Asp.net应用程序间实现SSO.我们也可以在不同系统不同平台间实现SSO,思想都是一样的,只不过实现起来需要创造性思维.

 

分享到:
评论

相关推荐

    ASP.NET跨域SSO解决方案

    ### ASP.NET跨域SSO解决方案 #### 一、引言 在现代企业级应用开发中,随着业务系统越来越复杂,各个子系统之间的交互也日益频繁。为了提高用户体验并简化管理流程,单点登录(Single Sign-On,简称SSO)成为了一个...

    SSO解决方案大全 Single Sign-On for everyone

    保护用户凭证的安全,防止会话劫持和跨站请求伪造(CSRF)攻击是设计SSO解决方案时必须解决的问题。此外,还应考虑故障恢复和审计跟踪,以确保系统的可靠性和合规性。 总之,SSO的实现涉及多个层面的技术,包括但不...

    .NET SSO解决方案

    .NET SSO解决方案主要涉及到的是如何在多个应用系统之间实现单点登录(Single Sign-On),这是一种让用户在访问由多个独立系统组成的企业级应用时只需要登录一次的技术。在ASP.NET MVC框架下,SSO可以帮助提高用户...

    SSO完整版跨域单点登录

    SSO(Single Sign-On)是...总之,这个SSO完整版的实现涵盖了从用户登录、票据生成到客户端验证的全过程,是一个全面的SSO解决方案。无论是学习SSO原理,还是进行企业级应用的开发,这个压缩包都提供了宝贵的参考资料。

    基于.Net的单点登录(SSO)解决方案

    总之,基于.Net的SSO解决方案涉及到了身份验证、令牌管理、跨域策略等多个技术层面。吴剑提供的源码可能简化了这些复杂性,但为了完全理解和利用这个解决方案,需要对.NET的身份验证机制有深入的理解,以及具备分析...

    完全跨域SSO

    解决方案通常有几种:CORS(跨源资源共享)、JSONP(JSON with Padding)或使用iframe和postMessage API来实现通信。 5. **Cookie管理**:TGT通常存储在cookie中,但由于跨域限制,我们需要在所有相关域上设置相同...

    完全跨域的单点登录 完全跨域的单点登录

    OpenAM则是一个开源的身份和访问管理解决方案,包含完整的SSO功能。 3. 跨域问题: 在Web应用中,由于浏览器的同源策略限制,不同域之间的数据无法直接共享。为实现跨域SSO,通常采用的方式是在认证服务器设置一个...

    详解可跨域的单点登录(SSO)实现方案【附.net代码】

    而可跨域的SSO解决方案则进一步解决了不同域名下的系统认证问题,使得用户跨域使用服务时也能实现统一的身份验证和授权。 本篇文章详细介绍了实现可跨域单点登录的技术方案,并提供了基于.net的代码实例,帮助读者...

    基于可变Cookie的跨域单点登录

    传统的单点登录解决方案大多依赖于固定的认证机制,如使用静态密钥进行Cookie加密,这种方式容易受到安全威胁,例如中间人攻击或重放攻击。基于可变Cookie的跨域单点登录方案通过以下几个关键步骤来克服这些问题: ...

    利用nginx解决cookie跨域访问的方法

    通过精心设计Nginx的代理规则和请求转发,我们可以绕过浏览器的限制,实现高效的跨域访问解决方案。在部署Nginx时,要注意对配置文件的准确设置以及后端应用的兼容性调整,以确保整个系统的顺利迁移和运行。

    详解cookie验证的php应用的一种SSO解决办法

    本文将详细介绍一种基于Cookie验证的PHP SSO解决方案。 首先,我们要理解Cookie验证的基本原理。当用户在登录系统时,服务器会生成一个包含用户信息的安全Token,通常是哈希值,存储在Cookie中。这个Token由服务器...

    基于.net的单点登录(sso)解决方案本科论文.doc

    综上所述,基于.NET的SSO解决方案涉及到的主要知识点包括身份验证服务设计、令牌机制、跨域策略、数据缓存管理、安全措施以及异常处理。通过合理的设计和实现,可以构建一个高效、安全的SSO系统,提供一致的用户体验...

    基于.Net的单点登录(SSO)解决方案-毕业设计论文.doc

    以下是对基于.Net的SSO解决方案的详细说明: 首先,主站是整个SSO系统的核心,负责处理用户的登录验证和令牌的发放。用户首次访问任何分站时,若未登录,会被重定向至主站进行身份验证。主站验证成功后,会生成一个...

    完全跨域单点登录DEMO

    综上所述,"完全跨域单点登录DEMO"提供了学习和实践SSO技术的平台,通过"WebSSODemo"和"WebSSOAuth"的代码,开发者可以深入理解SSO的实现原理,以及Cookie、JWT和OAuth 2.0在SSO中的应用,进一步提升Web应用的身份...

    单点登录的解决方案大全

    ### 单点登录(SSO)的解决方案大全 随着企业业务规模的不断扩大和技术架构的日益复杂,单点登录(Single Sign-On,简称SSO)已成为提高用户体验、简化管理流程的重要手段之一。本文将针对不同场景下的SSO实现方式...

    处理session跨域几种方案

    以下是一些处理Session跨域的常见方案: 1. **共享sessionId**: - **设置共同的Cookie**: 当用户在主域(如`.a.com`)登录后,服务器将Session ID作为Cookie设置,其Domain属性设置为主域,这样所有二级域名都...

    SSO单点登入,使用cookie实习(Struts2)

    SSO(Single Sign-On)单点登录是一种身份验证机制,允许用户在一次登录后访问多个相互关联的应用...在Struts2框架下,通过合理的设计和编程,我们可以构建一个高效、安全的SSO解决方案,实现跨系统的无缝登录体验。

Global site tag (gtag.js) - Google Analytics