跨域名单点登录 part 1 --设计蓝图
http://www.codeproject.com/KB/aspnet/CrossDomainSSOModel.aspx
一个asp.net应用程序独立域名单点登录的解决方案。
介绍
"某个星期一的早上,你在办公室中感叹周末过得好快,而这周又会是多艰难的一周的时候。你收到一封邮件了,当然不是一份好工作offer,
那只是你客户的另一个需求。你这个客户有好几个.net网站,他希望能够通过登录一个网站就登录其它所有的网站。当然,退出一个网站就要
退出所有的。
OK,所以你的客户需要引入“单点登录”。你可能想,这不是很难啊。asp.net的表单验证就可以完成这个功能,它允许同一域名下不同站点的程序
通过设置web.config中的<machineKey>段来共享cookie。于是你马上到google搜索,你也很快就能找到通过<machineKey>实现单点登录的很好例子。
OK,毕竟程序员也不是那么难混的一份工嘛。
等下,好像有什么东西引起你的注意。需求上说了,“你的客户有好几个网站,但是他们没说是在同一个域名下”。你刚刚没注意到这个最重要的问题,
于是,你的第一天就看起来不怎么顺利。不同域名下的站点要实现单点登录不是那么容易,最基本的问题就是,一个域名下的站点不可以跟其它站点
共享cookie.谁不知道啊,cookie是用来维护不同页面验证信息的吗?"
刚刚我描述的场景在现在是非常常见的。在web2.0和社区网站的时代,对的系统是非常罕见的。你经常可以在twitter上发一推,然后同时更新到LinkedIn
和Facebook而无需做其它操作。你也可能在CodeProject上写一篇文章,然后在一秒钟之内就在几百个网站分享到。所以说,很自然你就希望登录一个网站,
然后挑到另外一个相关网站的时候无需重新登录,无论这些站点是不是发布在同个域名下。
因此,我想如何才能开发出一种解决方案可以简单的实现单点登录(当然是asp.net网站)。人们已经通过不同的方法来实现该功能,同时你也可以购买上映解决方案。
但是,我希望实现的是简单的,免费的,最主要是能运行的方案。
Asp.net如何实现验证?
好吧,对于你来说这可能不是什么新内容。但是我们解决困难问题的时候,我们经常会回归基础研究其基本的工作原理。因此我们来重新看看asp.net的表单验证。
下面是asp.net表单验证的工作流程:
Figure : asp.net表单验证
操作顺序:
点击一个只能给验证过的用户访问的asp.net页面。
.net运行时在请求中查找一个cookie(表单验证cookie),如果无法找到cookie,那么重定向当前请求到登录也(登录页的位置配置在web.config)中。
输入登录信息并点击“登录”按钮。假设验证信息正确,你的程序会在数据库中验证这些信息,用户名赋值给Thread.CurrentPrincipal.Identity.Name属性。
并在response中输入一个cookie(里面包含这用户信息和cookie 名,以及超时时间等)然后再重定向到原来页面。
点击系统的另外一个页面(或者点击一个链接导航到其它页面),这次浏览器会发送验证cookie(还有其它一些之前该域名写入的cookie)因为上次请求时候
已经获得了验证cookie。
同样,asp.net在请求头中查找验证cookie,这次他就可以找到该cookie了。然后它会检查cookie的属性(想超时时间,路径等),如果没超时,则读取cookie值,
获取cookie中的用户信息,然后再次将用户名设置到Thread.CurrentPrincipal.Identity.Name属性,检测改用户是否具有访问该页面的全新,然后执行页面并返回给用户。
相当简单,不是吗?
同个域名下的不同站点的验证又是如何实现呢?
刚刚说过,asp.net的表单验证完全是依靠cookie。因此,如果两个不同的站点能够共享相同的cookie,那么就可以实现只登录一个网站就登录两个网站。
现在,http协议告诉我们两个不同的站点只有在两个站点都是同一域名下(或者子域名下)才可以共享cookie。在内部里,浏览器将cookies信息存放在本地(内存或者硬盘)与文章的url对应。
当你继续访问站点的其它链接时,浏览器会根据当前请求的URL的域名和本地cookie信息具有相同域名和子域名对比,如果相同则会发送这些cookie。
因此,我们假设你有下面两个站点:
这两个站点具有相同的主机地址(相同的域名 mydomain.com和子域名 www)同时两个站点都配置成使用表单验证的方式来验证用户。
假设,你登录了www.mydomain.com/site1站点,就像上面描述的,浏览器现在就有了来自www.mydomain.com/site1站点的表单验证cookie。
现在,如果你点击www.mydomain.com/site1站点的任何一个页面,表单验证cookie就会在请求中一起发送,为什么?是因为cookie是来源于
这个网站吗?是的,100%正确的。确切的说,是因为我们请求的url www.mydomain.com/site1和www.domain.com具有相同的子域名和域名(主机名)。
因此,在登录了www.mydomain.com/site1后,如果你请求www.mydomain.com/site2,同样的表单验证cookie(或者其它匹配的cookie)会在request中被发送
因为,www.mydomain.com/site2 和www.mydomain.com 即使他们是连个不同的程序(site2)。所以,同样的表单验证cookie可以给在同域名下两个不同的
web程序共享,通过登录一个站点来实现登录两个网站是可行的(也就是单点登录)。
然而,asp.net不会引入了表单认证就自动实现同个域名下的单点登录。这是为什么呢?因为两个不同的asp.net应用程序使用不同的加密的key来加密cookie数据
(和其它数据,像viewstate)来确保安全。因此,除非你为每个.net应用程序指定加密key,cookie就会从浏览器中发送,但是,程序不会从其它应用程序中读取验证cookie。
解决方案就是使用同样的验证key,对于asp.net应用,你必须在web.config中使用相同的<machineKey>字段:
<machineKey
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"/>
如果同一域名下的所有应用程序使用相同的machinekey(包含validationKey 和 decryptionKey),那么每个应用程序都可以读取其它应用的cookie信息。
如果两个网站在不同的域名或者子域名下呢?
假如你的站点不是发布在下面的主机地址下:
site1.mydomain.com
site2.mydomain.com
这两个站点共用相同的域名(相同的二级域名 mydomain.com)但是不同的三级域名(不同的子域名site1和site2)
浏览器默认只会发送仙童域名下的cookies(域名和子域名匹配)。因此,site1.mydomain.com站点不会获取到site2.mydomain.com的cookie(因为他们没有相同的主机地址,他们的子域名不同)
因急事你配置了相同的machinekey,也无法访问另一个域名的cookies信息。
所以,处理设置对所有的站点设置相同的machineKey之外,你还需要指定asp.net运行时去指定验证cookie的域名,以便于浏览器请求同个域名下的链接都能发送相关的cookies。
你必须对表单验证cookies做一下配置:
<forms name="name" loginUrl="URL" defaultUrl="URL" domain=”mydomain.com”/>
那么,如何在不同域名下共享验证cookie呢?
好的,那绝对没有这种方法。http协议出于安全的原因,阻止你从不同的域名下访问cookie。
假设,你有下列两个不同域名的网站:
www.domain1.com
www.domain2.com
现在,如果你使用表单验证登录了www.domain1.com站点,然后点击www.domain2.com的url,浏览器不会简单的将domain1.com下的cookie发送到domain2.com下。
因此,asp.net也没有相关的机制来实现不同站点下的单点登录。
为了在不同站点下实现单点登录,必须有某种解决办法或者实现模型让两个站点通过间接的机制访问同一个cookie。
一个最基本的SSO实现模型
假设我们必须在下列几个站点中实现SSO:
www.domain1.com
www.domain2.com
www.domain3.com
为了实现SSO,当用户在任何一个站点中验证后,我们必须让其它站点都在客户端浏览器中生成验证cookie
假如user1在www.domain1.com验证了,服务器返回请求数据之前,浏览器会将验证信息写到cookies中。但是我们同时还需要登录www.domain2.com和www.domain3.com。同样我们也需要
在客户端浏览器设置www.domain2.com和www.domain3.com的cookie。因此,在返回信息给浏览器时,www.domain1.com会重定向到www.domain2.com和www.domain3.com设置验证cookie。
下图描述了基本思想(箭头表示重定向):
Figure: 基础 SSO 模型概况
然后,下图标识的是详细的思想:
Figure : 基础SSO模型的登录时序图
时序操作:
用户点击www.domain1.com下的一个验证页面
[Status: browser has no authentication cookie]
浏览器相www.domain1.com发送一个没有验证cookie的请求(因为没有属于www.domain1.com域名的cookie)
[Status: browser has no authentication cookie]
www.domain1.com发现请求中没有验证cookie的信息,所以将请求重定向到www.domain1.com的登录页
[Status: browser has no authentication cookie]
用户输入登录信息,点击“登录”按钮,浏览器发送一个post请求到www.domain1.com
[Status: browser has no authentication cookie]
www.domain1.com接受登录信息,在数据存储地方验证登录信息,如果成功则意味这登录了,此时创建一个包含用户信息验证cookie,并添加到response中。
[Status : browser has no authentication cookie]
www.domain1.com重定向请求到www.domain2.com的一个页面下,而不是直接返回数据给浏览器,同时包含了一个指向www.domain1.com的返回连接ReturnUrl。等到验证cookie被设置到respose后。
cookie就会发送到浏览器端。
[Status : browser has no authentication cookie]
浏览器接受了包含验证cookie和一个重定向到www.domain2.com的命令的返回数据。因此,浏览器储存了www.domain2.com的验证cookie并发送一个带请求cookie的请求到www.domain2.com下。
[Status : browser has an authentication cookie for www.domain2.com]
www.domain2.com立刻重定向到ReturnUrl地址同时设置www.domain1.com的验证cookie。最后,验证cookie和重定向命令发送到客户端。
[Status : browser has an authentication cookie for www.domain2.com]
这时候浏览器接收了相应数据,其中包含了验证cookie已经重定向到www.domain1.com的命令。因此,浏览器把验证cookie存起来,
然后带着验证cookie发送重定向请求到www.domain1.com
[Status : browser has authentication cookie for both www.domain1.com and www.domain2.com].
www.domain1.com看到了验证cookie存在于request中,最后返回用户访问的页面,而不是登录页。
[Status : browser has authentication cookie for both www.domain1.com and www.domain2.com].
所以,如果用户在浏览器访问www.domain2.com下一个需要验证的页面,因为www.domain2.com域名下的验证cookie已经存在与浏览器中,
同时这些cookie信息连同请求一起发送到服务器,www.domain2.com检测cookie中的用户信息,然后使用户登录并访问用户请求的页面。
由于浏览器现在具有www.domain1.com和www.domain2.com两个站点的验证cookie,因此用户则已经登录了两个站点,最终,在两个站点中实现单点登录。
那么单点登出呢?
如果我们只是要实现用户登录多个网站,那么我们的任务已经完成了。但是最为单点登录的一部分,我们同时还需要实现单点登出。也就是说,用户退出一个网站
则这个用于必须退出所有的网站。
在内部,用户登出一个网站(假设 www.domain1.com),其它网站的验证cookie也要相应的移除(这个例子中是www.domain2.com).cookie可以在发送返回数据给浏览器之前
移除。
要移除所有网站的验证cookie,只需继续使用我们上面提到的request-redirect-response流程,只是现在不是添加cookie,而是移除cookie。
SSO模型的问题
这个模型如果只有两个网站没什么问题。登录或登出一个站点,相应的其它站点(SSO下的站点)内部需要遵循request-redirect-response这样的流程。
一定用户登录一个站点,其它站点就会发送后续的请求,这里并不会出现request-redirect-response循环,因为每个站点都 有他们的独立的验证cookie。
但是,如果有超过两个的站点,那么复杂度也就提高了。就是说,如果用户登录www.domain1.com,站点会重定向到www.domain2.com和www.domain3.com去设置验证cookie
最后www.domain3.com会重定向到www.domain1.com一开始请求的页面,并返回给浏览器。这回是的登录和登出功能变得很好资源又很复杂。如果你超过3个网站呢?如果超过20个
网站要在同一个单点登录模型中呢?这个模型很明显不太合适。
这个模型要求每个站点都知道所有SSO下的其它的站点(因为每个站点都需要重定向都其它站点去设置验证cookie)。同时,也要求每个网站都需要实现用户的验证功能。
这可以很清楚的理解,随着站点数目的增加,SSO模型会变得混乱,变得无法接受,因此该模型不可以认为是跨域应用程序的通用SSO模型。因此,我们需要一个不受网站数量
影响的更好模型。
跨域SSO的推荐模型
之前提到的模型有点混乱,因为每个站点都必须重定向到N-1个其它网站,然后连续移除各站点验证cookie。因此,每个站点都要配置其它N-1站点的相关信息,同时还必须实现复杂的登录和登出逻辑。
如果当我们要验证所有网站的cookie是只维护一个单独的验证会怎样呢?又如果引入一个独立的新站点来管理用户的认证以及设置验证cookie?听起来赚到了。
为了在多个站点引入SSO,用户数据库必须是统一的,通过专门的网站来实现用户验证和验证功能,然后其它网站通过web service或者WCF服务来访问这些功能。
因此,这可以减少其它冗余的用户验证/验证功能。但是最主要的我难题是,这个独立的网站怎么实现单点登录呢?
在这个模型中,浏览器会不会存储各个网站的验证cookie。而是它会保存这个专门用于实现单点登录的网站的cookie。这里就叫做:www.sso.com。
在这个模型中,每个对任何站点的请求(SSO模型中的页面)都会被重定向到SSO站点(www.sso.com)去设置和检测现有的验证cookie。如果找到cookie,
那么验证过的页面就会发送到浏览器,如果没发现,用户就会重定向到相应站点的登录页面。
下图描述的这个基本思想:
Figure : 在推荐模型中使用单独(www.sso.com)的站点管理验证cookie
为了更好的理解这个模型,我们假设需要在下面两个站点中实现该模型:
www.domain1.com
www.domain2.com
然后,现在我们需要一个独立管理验证cookie的站点
这里就是这个模型的工作流程:
Figure : 没有登录时访问页面的时序图
操作流程:
用户访问www.domain1.com一个需要验证的页面
www.domain1.com重定向的www.sso.com并且在url中添加了一个ReturnUrl参数设置成原来的URL
www.sso.com检测是否存在验证cookie,或者请求中是否包含有用户Token。如果没有,那么重定向到www.domain1.com,同时包含一个标识知名用户需要登录。同时它还附加了一个
ReturnUrl参数。
www.domain1.com检查从www.sso.com重定向过来的url参数。url参数标识了用户验证没有发现,然后它就重定向到www.domain1.com的登录页面,同时在请求中标识不重定向到www.sso.com
Figure : 登录时序图
用户输入登录信息并点击“登录”按钮。在这个模型中此时不重定向到SSO站点。这次,www.domain1.com调用www.sso.com提供的webservice或WCF服务检查用户信息是否正确,如果正确,则返回
用户对象以及一个用户每次登录是都会生成的Token(比方说GUID)的属性。
www.domain1.com标识该用户已经登录(例如,将用户对象存放到session中),此时需要构建一个包含用户Token和以及返回原来页面的ReturnUrl参数,然后重定向到www。sso.com去设置验证cookie。
www.sso.com检查请求过来的url并发现了用户Token,现在还没有发现验证cookie。这表面,用户已经在客户端(www.domain1.com)验证通过,现在需要将www.sso.com的验证cookie设置到客户端浏览器。
因此,他通过Token用缓存中查询用户信息,准备验证cookie需要用的用户信息,并设置cookie属性(超时时间等),并把cookie添加到response中,最后在重定向回ReturnUrl(www.domain1.com的链接)中检查到的原来页面。
并附带Token在url中。
浏览器获得一个重定向命令,同时包含指向www.domain.com的ReturnUrl,和www.sso.com的cookie。因此,他保存了www.sso.com的验证cookie,并请求www.domain1.com的链接。
www.domain1.com现在可以看到url参数的用户Token。他通过调用www.sso.com调用web/WCF服务来验证用户,如果验证通过,则执行原来对www.domain1.com页面的请求,然后输出到浏览器中。
Figure : 登录后,浏览一个需要验证的页面的时序图
用户点击www.domain2.com一个需要验证的url
www.domain2.com重定向到www.sso.com,同时附带原来访问页面的ReturnUrl作为参数。
浏览器得到一个重定向www.sso.com的命令,但是他已经存在一个验证cookie存储在浏览器,因此在请求到www.sso.com时候会先把cookie附加到请求。
www.sso.com检查请求是否存在验证cookie。如果检测到验证cookie,那么检测是否过期。如果没过期,在根据用户Token,并重定向到www.domain2.com原来的页面,执行原来请求的www.domain2.com的页面
并输出到客户端浏览器。
总结,
哇,听起来发生了好多事,让我在这里总结一下:
一开始,浏览器没有www.sso.com的任何验证cookie,因此点击www.domain1.com或www.domain2.com的任何需要验证的页面时,都会重定向到登录页面(通过内部重定向到www.sso.com检测验证cookie是否存在)
一旦用户登录一个站点,www.sso.com就会在客户端浏览器设置一个验证cookie,包含用户信息(最主要是:用于检测用户的登录session的用户Token)。
现在,如果你点击www.domain1.com或者www.domain2.com的任何一个验证页面,请求会被重定向到www.sso.com,并且浏览器也会发送验证cookie,该cookie是之前设置的,提取了用户的Token,然后重定向回原来的url,
并在url中附带Token,执行并返回给浏览器。
一定用户登录SSO模型中的任何一个站点,点击www.domain1.com或www.domain2.com下任何需要验证的页面,都会重定向到www.sso.com(检查验证cookie和检索Token),然后执行验证页面并输出到浏览器。