﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-昊天-随笔分类-SSO</title><link>http://www.blogjava.net/hao446tian/category/49875.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 19 Oct 2011 06:59:01 GMT</lastBuildDate><pubDate>Wed, 19 Oct 2011 06:59:01 GMT</pubDate><ttl>60</ttl><item><title>SSO单点登录解决方案[转载] </title><link>http://www.blogjava.net/hao446tian/archive/2011/10/19/361577.html</link><dc:creator>昊天</dc:creator><author>昊天</author><pubDate>Wed, 19 Oct 2011 06:57:00 GMT</pubDate><guid>http://www.blogjava.net/hao446tian/archive/2011/10/19/361577.html</guid><wfw:comment>http://www.blogjava.net/hao446tian/comments/361577.html</wfw:comment><comments>http://www.blogjava.net/hao446tian/archive/2011/10/19/361577.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hao446tian/comments/commentRss/361577.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hao446tian/services/trackbacks/361577.html</trackback:ping><description><![CDATA[<strong>1 什么是单点登陆<br /></strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 单点登录（Single Sign On），简称为 SSO，是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中，用户只需要登录一次就可以访问所有相互信任的应用系统。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 较大的企业内部，一般都有很多的业务支持系统为其提供相应的管理和IT服务。例如财务系统为财务人员提供财务的管理、计算和报表服务；人事系统为人事部门 提供全公司人员的维护服务；各种业务系统为公司内部不同的业务提供不同的服务等等。这些系统的目的都是让<a onclick="javascript:tagshow(event, '%BC%C6%CB%E3%BB%FA');" href="javascript:;" target="_self"><u><strong>计算机</strong></u></a>来进行复杂繁琐的计算工作，来替代人力的手 工劳动，提高工作效率和质量。这些不同的系统往往是在不同的时期建设起来的，运行在不同的平台上；也许是由不同厂商开发，使用了各种不同的技术和标准。如 果举例说国内一著名的IT公司（名字隐去），内部共有60多个业务系统，这些系统包括两个不同版本的SAP的ERP系统，12个不同类型和版本的数据库系 统，8个不同类型和版本的操作系统，以及使用了3种不同的防火墙技术，还有数十种互相不能兼容的协议和标准，你相信吗？不要怀疑，这种情况其实非常普遍。 每一个应用系统在运行了数年以后，都会成为不可替换的企业IT架构的一部分，如下图所示。<br /><a href="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image002.gif"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image002.gif" width="520" /></a><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 随着企业的发展，业务系统的数量在不断的增加，老的系统却不能轻易的替换，这会带来很多的开销。其一是管理上的开销，需要维护的系统越来越多。很多系统的 数据是相互冗余和重复的，数据的不一致性会给管理工作带来很大的压力。业务和业务之间的相关性也越来越大，例如公司的计费系统和财务系统，财务系统和人事 系统之间都不可避免的有着密切的关系。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了降低管理的消耗，最大限度的重用已有投资的系统，很多企业都在进行着企业应用集成（EAI）。企业应用集成可以在不同层面上进行：例如在数据存储层面 上的&#8220;数据大集中&#8221;，在传输层面上的&#8220;通用数据交换平台&#8221;，在应用层面上的&#8220;业务流程整合&#8221;，和用户界面上的&#8220;通用企业门户&#8221;等等。事实上，还用一个层面 上的集成变得越来越重要，那就是&#8220;身份认证&#8221;的整合，也就是&#8220;单点登录&#8221;。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通常来说，每个单独的系统都会有自己的安全体系和身份认证系统。整合以前，进入每个系统都需要进行登录，这样的局面不仅给管理上带来了很大的困难，在安全方面也埋下了重大的隐患。下面是一些著名的调查公司显示的统计数据： 
<ul><li>用户每天平均16分钟花在身份验证任务上 - 资料来源：IDS</li><li>频繁的IT用户平均有21个密码 - 资料来源：NTA Monitor Password Survey</li><li>49%的人写下了其密码，而67%的人很少改变它们</li><li>每79秒出现一起身份被窃事件 - 资料来源：National Small Business Travel Assoc</li><li>全球欺骗损失每年约12B - 资料来源：Comm Fraud Control Assoc</li><li>到2007年，身份管理市场将成倍增长至$4.5B - 资料来源：IDS </li></ul><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用&#8220;单点登录&#8221;整合后，只需要登录一次就可以进入多个系统，而不需要重新登录，这不仅仅带来了更好的用户体验，更重要的是降低了安全的风险和管理的消耗。请看下面的统计数据： 
<ul><li>提高IT效率：对于每1000个受管用户，每用户可节省$70K</li><li>帮助台呼叫减少至少1/3，对于10K员工的公司，每年可以节省每用户$75，或者合计$648K</li><li>生产力提高：每个新员工可节省$1K，每个老员工可节省$350 - 资料来源：Giga</li><li>ROI回报：7.5到13个月 - 资料来源：Gartner&nbsp;&nbsp;&nbsp; </li></ul><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另外，使用&#8220;单点登录&#8221;还是<a onclick="javascript:tagshow(event, 'SOA');" href="javascript:;" target="_self"><u><strong>SOA</strong></u></a>时代的需求之一。在面向服务的架构中，服务和服务之间，程序和程序之间的通讯大量存在，服务之间的安全认证是SOA应用的难点之一，应此建立&#8220;单点登录&#8221;的系统体系能够大大简化SOA的安全问题，提高服务之间的合作效率。<br />&nbsp;&nbsp;&nbsp;<strong>2 单点登陆的技术实现机制</strong><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 随着SSO技术的流行，SSO的产品也是满天飞扬。所有著名的软件厂商都提供了相应的解决方案。在这里我并不想介绍自己公司（<a onclick="javascript:tagshow(event, 'Sun');" href="javascript:;" target="_self"><u><strong>Sun</strong></u></a> Microsystems）的产品，而是对SSO技术本身进行解析，并且提供自己开发这一类产品的方法和简单演示。有关我写这篇文章的目的，请参考我的博 客（<a href="http://yuwang881.blog.sohu.com/3184816.html">http://yuwang881.blog.sohu.com/3184816.html</a>）。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 单点登录的机制其实是比较简单的，用一个现实中的例子做比较。颐和园是北京著名的旅游景点，也是我常去的地方。在颐和园内部有许多独立的景点，例如&#8220;苏州 街&#8221;、&#8220;佛香阁&#8221;和&#8220;德和园&#8221;，都可以在各个景点门口单独买票。很多游客需要游玩所有德景点，这种买票方式很不方便，需要在每个景点门口排队买票，钱包拿 进拿出的，容易丢失，很不安全。于是绝大多数游客选择在大门口买一张通票（也叫套票），就可以玩遍所有的景点而不需要重新再买票。他们只需要在每个景点门 口出示一下刚才买的套票就能够被允许进入每个独立的景点。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 单点登录的机制也一样，如下图所示，当用户第一次访问应用系统1的时候，因为还没有登录，会被引导到认证系统中进行登录（1）；根据用户提供的登录信息， 认证系统进行身份效验，如果通过效验，应该返回给用户一个认证的凭据－－ticket（2）；用户再访问别的应用的时候（3，5）就会将这个ticket 带上，作为自己认证的凭据，应用系统接受到请求之后会把ticket送到认证系统进行效验，检查ticket的合法性（4，6）。如果通过效验，用户就可 以在不用再次登录的情况下访问应用系统2和应用系统3了。<br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image004.gif" />&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从上面的视图可以看出，要实现SSO，需要以下主要的功能： 
<ul><li>所有应用系统共享一个身份认证系统。<br />统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较，对用户进行登录认证；认证成功后，认证系统应该生成统一的认证标志（ticket），返还给用户。另外，认证系统还应该对ticket进行效验，判断其有效性。</li><li>所有应用系统能够识别和提取ticket信息<br />要实现SSO的功能，让用户只登录一次，就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取，通过与认证系统的通讯，能自动判断当前用户是否登录过，从而完成单点登录的功能。&nbsp;&nbsp;&nbsp; </li></ul><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 上面的功能只是一个非常简单的SSO架构，在现实情况下的SSO有着更加复杂的结构。有两点需要指出的是： 
<ul><li>单一的用户信息数据库并不是必须的，有许多系统不能将所有的用户信息都集中存储，应该允许用户信息放置在不同的存储中，如下图所示。事实上，只要统一认证系统，统一ticket的产生和效验，无论用户信息存储在什么地方，都能实现单点登录。&nbsp;&nbsp;&nbsp;&nbsp; </li></ul><br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image006.gif" /><br />
<ul><li>统一的认证系统并不是说只有单个的认证服务器，如下图所示，整个系统可以存在两个以上的认证服务器，这些服务器甚至可以是不同的产品。认证服务器 之间要通过标准的通讯协议，互相交换认证信息，就能完成更高级别的单点登录。如下图，当用户在访问应用系统1时，由第一个认证服务器进行认证后，得到由此 服务器产生的ticket。当他访问应用系统4的时候，认证服务器2能够识别此ticket是由第一个服务器产生的，通过认证服务器之间标准的通讯协议 （例如SAML）来交换认证信息，仍然能够完成SSO的功能。 </li></ul><br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image008.gif" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;<strong>3 WEB-SSO的实现</strong><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 随着互联网的高速发展，<a onclick="javascript:tagshow(event, 'WEB');" href="javascript:;" target="_self"><u><strong>WEB</strong></u></a>应用几乎统治了绝大部分的软件应用系统，因此WEB-SSO是SSO应用当中最为流行。WEB-SSO有其自身的特点和优 势，实现起来比较简单易用。很多商业软件和<a onclick="javascript:tagshow(event, '%BF%AA%D4%B4');" href="javascript:;" target="_self"><u><strong>开源</strong></u></a>软件都有对WEB-SSO的实现。其中值得一提的是OpenSSO （<a href="https://opensso.dev.java.net/">https://opensso.dev.java.net</a>），为用<a onclick="javascript:tagshow(event, 'Java');" href="javascript:;" target="_self"><u><strong>Java</strong></u></a>实现WEB-SSO提供架构指南和服务指南，为用户自己来实现WEB-SSO提供了理论的依据和实现的方法。&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为什么说WEB-SSO比较容易实现呢？这是有WEB应用自身的特点决定的。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 众所周知，Web协议（也就是HTTP）是一个无状态的协议。一个Web应用由很多个Web页面组成，每个页面都有唯一的URL来定义。用户在浏览器的地 址栏输入页面的URL，浏览器就会向Web <a onclick="javascript:tagshow(event, 'Server');" href="javascript:;" target="_self"><u><strong>Server</strong></u></a>去发送请求。如下图，浏览器向Web服务器发送了两个请求，申请了两个页面。这两个页面的请求是分别使用了两个单独的HTTP连接。所谓无状 态的协议也就是表现在这里，浏览器和Web服务器会在第一个请求完成以后关闭连接通道，在第二个请求的时候重新建立连接。Web服务器并不区分哪个请求来 自哪个客户端，对所有的请求都一视同仁，都是单独的连接。这样的方式大大区别于传统的（Client/Server）C/S结构,在那样的应用中，客户端 和服务器端会建立一个长时间的专用的连接通道。正是因为有了无状态的特性，每个连接资源能够很快被<a onclick="javascript:tagshow(event, '%C6%E4%CB%FB');" href="javascript:;" target="_self"><u><strong>其他</strong></u></a>客户端所重用，一台Web服务器才能够同时服务于成 千上万的客户端。<br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image010.gif" /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是我们通常的应用是有状态的。先不用提不同应用之间的SSO，在同一个应用中也需要保存用户的登录身份信息。例如用户在访问页面1的时候进行了登录，但 是刚才也提到，客户端的每个请求都是单独的连接，当客户再次访问页面2的时候，如何才能告诉Web服务器，客户刚才已经登录过了呢？浏览器和服务器之间有 约定：通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串，并且可以保存在浏览器中。如下图所示，当浏览器访问了 页面1时，web服务器设置了一个cookie，并将这个cookie和页面1一起返回给浏览器，浏览器接到cookie之后，就会保存起来，在它访问页 面2的时候会把这个cookie也带上，Web服务器接到请求时也能读出cookie的值，根据cookie值的内容就可以判断和恢复一些用户的信息状 态。<br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image012.gif" /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Web-SSO完全可以利用Cookie结束来完成用户登录信息的保存，将浏览器中的Cookie和上文中的Ticket结合起来，完成SSO的功能。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了完成一个简单的SSO的功能，需要两个部分的合作： 
<ul><li>统一的身份认证服务。</li><li>修改Web应用，使得每个应用都通过这个统一的认证服务来进行身份效验。 </li></ul><br />&nbsp;&nbsp;&nbsp;<strong>3.1 Web SSO 的样例</strong><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 根据上面的原理，我用<a onclick="javascript:tagshow(event, 'J2EE');" href="javascript:;" target="_self"><u><strong>J2EE</strong></u></a>的技术（<a onclick="javascript:tagshow(event, 'JSP');" href="javascript:;" target="_self"><u><strong>JSP</strong></u></a>和Servlet）完成了一个具有Web-SSO的简单样例。样例包含一个身份认证的服务器和两个简单的 Web应用，使得这两个 Web应用通过统一的身份认证服务来完成Web-SSO的功能。此样例所有的源代码和二进制代码都可以从网站地址<a href="http://gceclub.sun.com.cn/wangyu/">http://gceclub.sun.com.cn/wangyu/</a>下载。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 样例下载、安装部署和运行指南： 
<ul><li>Web-SSO的样例是由三个标准Web应用组成，压缩成三个zip文件，从<a href="http://gceclub.sun.com.cn/wangyu/web-sso/">http://gceclub.sun.com.cn/wangyu/web-sso/</a>中下载。其中SSOAuth（<a href="http://gceclub.sun.com.cn/wangyu/web-sso/SSOAuth.zip">http://gceclub.sun.com.cn/wangyu/web-sso/SSOAuth.zip</a>）是身份认证服务；SSOWebDemo1（<a href="http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo1.zip">http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo1.zip</a>）和SSOWebDemo2（<a href="http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo2.zip">http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo2.zip</a>） 是两个用来演示单点登录的Web应用。这三个Web应用之所以没有打成war包，是因为它们不能直接部署，根据读者的部署环境需要作出小小的修改。样例部 署和运行的环境有一定的要求，需要符合Servlet2.3以上标准的J2EE容器才能运行（例如Tomcat5,Sun Application Server 8, Jboss 4等）。另外，身份认证服务需要JDK1.5的运行环境。之所以要用JDK1.5是因为笔者使用了一个线程安全的高性能的Java集合类 &#8220;ConcurrentMap&#8221;，只有在JDK1.5中才有。</li><li>这三个Web应用完全可以单独部署，它们可以分别部署在 不同的机器，不同的操作系统和不同的J2EE的产品上，它们完全是标准的和平台无关的应用。但是有一个限制，那两台部署应用（demo1、demo2）的 机器的域名需要相同，这在后面的章节中会解释到cookie和domain的关系以及如何制作跨域的WEB-SSO<br />解压缩SSOAuth.zip文件，在/WEB-INF/下的web.xml中请修改&#8220;domainname&#8221;的属性以反映实际的应用部署情况， domainname需要设置为两个单点登录的应用（demo1和demo2）所属的域名。这个domainname和当前SSOAuth服务部署的机器 的域名没有关系。我缺省设置的是&#8220;.sun.com&#8221;。如果你部署demo1和demo2的机器没有域名，请输入IP地址或主机名（如 localhost），但是如果使用IP地址或主机名也就意味着demo1和demo2需要部署到一台机器上了。设置完后，根据你所选择的J2EE容器， 可能需要将SSOAuth这个目录压缩打包成war文件。用&#8220;jar -cvf SSOAuth.war SSOAuth/&#8221;就可以完成这个功能。</li><li>解压缩SSOWebDemo1和SSOWebDemo2文件，分别在它们/WEB-INF/下找到web.xml文件，请修改其中的几个初始化参数 </li></ul><br />&lt;init-param&gt;<br />&lt;param-name&gt;SSOServiceURL&lt;/param-name&gt;<br />&lt;param-value&gt;http://wangyu.prc.sun.com:8080/SSOAuth/SSOAuth&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;SSOLoginPage&lt;/param-name&gt;<br />&lt;param-value&gt;http://wangyu.prc.sun.com:8080/SSOAuth/login.jsp&lt;/param-value&gt;<br />&lt;/init-param&gt; 
<ul><li>将其中的SSOServiceURL和SSOLoginPage修改成部署SSOAuth应用的机器名、端口号以及根路径（缺省是 SSOAuth）以反映实际的部署情况。设置完后，根据你所选择的J2EE容器，可能需要将SSOWebDemo1和SSOWebDemo2这两个目录压 缩打包成两个war文件。用&#8220;jar -cvf SSOWebDemo1.war SSOWebDemo1/&#8221;就可以完成这个功能。</li><li>请输入第一个web应用的测试URL（test.jsp）,例如<a href="http://wangyu.prc.sun.com:8080/">http://wangyu.prc.sun.com:8080/</a>SSOWebDemo1/test.jsp，如果是第一次访问，便会自动跳转到登录界面，如下图。 </li></ul><br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image014.gif" /> 
<ul><li>使用系统自带的三个帐号之一登录（例如，用户名：wangyu,密码：wangyu），便能成功的看到test.jsp的内容：显示当前用户名和欢迎信息。 </li></ul><br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image016.gif" width="520" /> 
<ul><li>请接着在同一个浏览器中输入第二个web应用的测试URL（test.jsp）,例如<a href="http://wangyu.prc.sun.com:8080/">http://wangyu.prc.sun.com:8080/</a>SSOWebDemo2/test.jsp。你会发现，不需要再次登录就能看到test.jsp的内容，同样是显示当前用户名和欢迎信息，而且欢迎信息中明确的显示当前的应用名称（demo2）。&nbsp;&nbsp;&nbsp; </li></ul><br /><img title="点击图片可在新窗口打开" style="cursor: pointer" alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/javachannel/image018.gif" width="520" /><br />&nbsp;&nbsp;&nbsp;<strong>3.2 WEB-SSO代码讲解</strong><br /><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.2.1身份认证服务代码解析</strong><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Web-SSO的源代码可以从网站地址<a href="http://gceclub.sun.com.cn/wangyu/web-sso/websso_src.zip">http://gceclub.sun.com.cn/wangyu/web-sso/websso_src.zip</a>下 载。身份认证服务是一个标准的web应用，包括一个名为SSOAuth的Servlet，一个login.jsp文件和一个failed.html。身份 认证的所有服务几乎都由SSOAuth的Servlet来实现了；login.jsp用来显示登录的页面（如果发现用户还没有登录过）； failed.html是用来显示登录失败的信息（如果用户的用户名和密码与信息数据库中的不一样）。<br />&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SSOAuth的代码如下面的列表显示，结构非常简单，先看看这个Servlet的主体部分：<br />package DesktopSSO;<br /><br />import java.io.*;<br />import java.net.*;<br />import java.text.*;<br />import java.util.*;<br />import java.util.concurrent.*;<br /><br />import javax.servlet.*;<br />import javax.servlet.http.*;<br /><br /><br />public class SSOAuth extends HttpServlet {<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static private ConcurrentMap accounts;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static private ConcurrentMap SSOIDs;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String cookiename="WangYuDesktopSSOID";<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String domainname;<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void init(ServletConfig config) throws ServletException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.init(config);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domainname= config.getInitParameter("domainname");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cookiename = config.getInitParameter("cookiename");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SSOIDs = new ConcurrentHashMap();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; accounts=new ConcurrentHashMap();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; accounts.put("wangyu", "wangyu");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; accounts.put("paul", "paul");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; accounts.put("carol", "carol");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PrintWriter ut = response.getWriter();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String action = request.getParameter("action");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String result="failed";<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (action==null) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handlerFromLogin(request,response);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else if (action.equals("authcookie")){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String myCookie = request.getParameter("cookiename");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (myCookie != null) result = authCookie(myCookie);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.print(result);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.close();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else if (action.equals("authuser")) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result=authNameAndPasswd(request,response);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.print(result);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.close();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else if (action.equals("logout")) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String myCookie = request.getParameter("cookiename");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; logout(myCookie);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.close();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />.....<br /><br />}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从代码很容易看出，SSOAuth就是一个简单的Servlet。其中有两个静态成员变量：accounts和SSOIDs，这两个成员变量都使用了 JDK1.5中线程安全的MAP类： ConcurrentMap，所以这个样例一定要JDK1.5才能运行。Accounts用来存放用户的用户名和密码，在init()的方法中可以看到我 给系统添加了三个合法的用户。在实际应用中，accounts应该是去数据库中或LDAP中获得，为了简单起见，在本样例中我使用了 ConcurrentMap在内存中用程序创建了三个用户。而SSOIDs保存了在用户成功的登录后所产生的cookie和用户名的对应关系。它的功能显 而易见：当用户成功登录以后，再次访问别的系统，为了鉴别这个用户请求所带的cookie的有效性，需要到SSOIDs中检查这样的映射关系是否存在。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在主要的请求处理方法processRequest()中，可以很清楚的看到SSOAuth的所有功能。 
<ul><li>如果用户还没有登录过，是第一次登录本系统，会被跳转到login.jsp页面（在后面会解释如何跳转）。用户在提供了用户名和密码以后，就会用handlerFromLogin()这个方法来验证。</li><li>如果用户已经登录过本系统，再访问别的应用的时候，是不需要再次登录的。因为浏览器会将第一次登录时产生的cookie和请求一起发送。效验cookie的有效性是SSOAuth的主要功能之一。</li><li>SSOAuth还能直接效验非login.jsp页面过来的用户名和密码的效验请求。这个功能是用于非web应用的SSO，这在后面的桌面SSO中会用到。</li><li>SSOAuth还提供logout服务。&nbsp;&nbsp;&nbsp; </li></ul>
<p><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 下面看看几个主要的功能函数：<br />private void handlerFromLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String username = request.getParameter("username");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String password = request.getParameter("password");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String pass = (String)accounts.get(username);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((pass==null)||(!pass.equals(password)))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getServletContext().getRequestDispatcher("/failed.html").forward(request, response);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String gotoURL = request.getParameter("goto");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String newID = createUID();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SSOIDs.put(newID, username);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cookie wangyu = new Cookie(cookiename, newID);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wangyu.setDomain(domainname);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wangyu.setMaxAge(60000);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wangyu.setValue(newID);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wangyu.setPath("/");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.addCookie(wangyu);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("login success, goto back url:" + gotoURL);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (gotoURL != null) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PrintWriter ut = response.getWriter();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.sendRedirect(gotoURL);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.close();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handlerFromLogin()这个方法是用来处理来自login.jsp的登录请求。它的逻辑很简单：将用户输入的用户名和密码与预先设定好的用 户集合（存放在accounts中）相比较，如果用户名或密码不匹配的话，则返回登录失败的页面（failed.html），如果登录成功的话，需要为用 户当前的session创建一个新的ID，并将这个ID和用户名的映射关系存放到SSOIDs中，最后还要将这个ID设置为浏览器能够保存的cookie 值。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 登录成功后，浏览器会到哪个页面呢？那我们回顾一下我们是如何使用身份认证服务的。一般来说我们不会直接访问身份服务的任何URL，包括 login.jsp。身份服务是用来保护其他应用服务的，用户一般在访问一个受SSOAuth保护的Web应用的某个URL时，当前这个应用会发现当前的 用户还没有登录，便强制将也页面转向SSOAuth的login.jsp，让用户登录。如果登录成功后，应该自动的将用户的浏览器指向用户最初想访问的那 个URL。在handlerFromLogin()这个方法中，我们通过接收&#8220;goto&#8221;这个参数来保存用户最初访问的URL，成功后便重新定向到这个页 面中。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另外一个要说明的是，在设置cookie的时候，我使用了一个setMaxAge(6000)的方法。这个方法是用来设置cookie的有效期，单位是 秒。如果不使用这个方法或者参数为负数的话，当浏览器关闭的时候，这个cookie就失效了。在这里我给了很大的值（1000分钟），导致的行为是：当你 关闭浏览器（或者关机），下次再打开浏览器访问刚才的应用，只要在1000分钟之内，就不需要再登录了。我这样做是下面要介绍的桌面SSO中所需要的功 能。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其他的方法更加简单，这里就不多解释了。</p>
<div class="articleText"><strong>3.2.2具有SSO功能的web应用源代码解析</strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 要实现WEB-SSO的功能，只有身份认证服务是不够的。这点很显然，要想使多个应用具有单点登录的功能，还需要每个应用本身的配合：将自己的身份认证的 服务交给一个统一的身份认证服务－SSOAuth。SSOAuth服务中提供的各个方法就是供每个加入SSO的Web应用来调用的。<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />再转载点内容：<br /><br /><br />
<div class="postText">
<p>昨天和几位朋友探讨到了这个话题，发现虽然单点登录，或者叫做独立的passport登录虽然已经有了很多实现方法，但是能真正了解并实现的人却并不太多，所以些下此文，希望从原理到实现，能让大家了解的多一些</p>
<p><br />至于什么是单点登录，举个例子，如果你登录了msn messenger，访问hotmail邮件就不用在此登录。<br />一般单点登录都需要有一个独立的登录站点,一般具有独立的域名，专门的进行注册，登录，注销等操作</p>
<p>我们为了讨论方便，把这个登录站点叫做站点P，设其Url为<a href="http://passport.yizhu2000.com/">http://passport.yizhu2000.com</a>，需要提供服务的站点设为A和B，跨站点单点登录是指你在A网站进行登录后，使用B网站的服务就不需要再登录</p>
<p><br />从技术角度讲单点登录分为：</p>
<ul><li>跨子域单点登录</li><li>完全跨单点域登录 </li></ul>
<h2>跨子域单点登录</h2>
<p>所谓跨子域登录，A，B站点和P站点位于同一个域下面，比如A站点为http://blog.yizhu2000.com&nbsp;&nbsp;&nbsp;&nbsp; B站点为 <a href="http://forum.yizhu2000.com/">http://forum.yizhu2000.com</a>,他们和登录站点P的关系可以看到，都是属于同一个父域，yizhu2000.com,不同的是子域不同，一个为blog，一个为forum，一个是passport</p>
<p>我们先看看最常用的非跨站点普通登录的情况，一般登录验证通过后，一般会将你的用户名和一些用户信息，通过某一密钥进行加密，写在本地，也就是一个加密的cookie，我们把这个cookie叫做--票（ticket）。</p>
<p>需要判断用户是否登录的页面，需要读取这个ticket，并从其中解密出用户信息，如果ticket不存在，或者无法解密，意味着用户没有登录，或者登录信息不正确，这时就要跳转到登录页面进行登录，在这里加密的作用有两个，一是防止用户信息被不怀好意者看到，二是保证ticket不会被伪造，后者其实更为重要，加密后，各个应用需要采用与加密同样的密钥进行解密，如果不知道密钥，就不能伪造出ticket，</p>
<p>（注：加密和解密的密钥有可能不同，取决于采用什么加密算法，如果是对称加密，则为同一密钥，如果是非对称，就不同了，一般用私钥加密，公钥解密，但是无论怎样，密钥都只有内部知道，这样伪造者既无法伪造也无法解密ticket）</p>
<p>跨子域的单点登录，和上述普通登录的过程没有什么不同，唯一不同的是写cookie时，由于登录站点P和应用A处于不同的子域，P站写入的cookie的域为passport.yizhu2000.net，而A站点为forum.yizhu2000.net，A在判断用户登录时无法读到P站点的ticket</p>
<p>解决方法非常简单，当Login完成后P站点写ticket的时候，只需把cookie的域设为他们共同的父域，yizhu2000.net就可以了：cookie.domain="yizhu2000.net"，A站点自然就可以读到这个ticket了</p>
<p>ASP。Net的form验证本身实现了这个机制，大家可以参考http://blog.csdn.net/octverve/archive/2007/09/22/1796338.aspx</p>
<p><em><font color="#400080">ASP.NET身份验证信息跨域共享状态</font></em> </p>
<p><em><font color="#400080">在ASP.NET 2.0 中只需修改web.config文件即可，修改方法如下:</font></em> </p>
<p><em><font color="#400080">&lt;authentication mode="Forms"&gt; <br />&lt;forms name=".ASPNETFORM"&nbsp;&nbsp; domain="imneio.com" loginUrl="/login.aspx" defaultUrl="/default.aspx" protection="All" timeout="30" path="/" requireSSL="false" slidingExpiration="true" enableCrossAppRedirects="false" cookieless="UseDeviceProfile" /&gt; <br />&lt;/authentication&gt;</font></em> </p>
<p><em><font color="#400080">domain指定了cookie保存的域，只要保存的是 abc.com形式或者.abc.com的形式，那么其二级域名都可以共享此cookie。</font></em> </p>
<p><em><font color="#400080">此外，web.config标签中的&lt;sessionState &gt;也做相应修改，mode改为StateServer或者SqlServer，那么里面的session信息也就全部可以共享了。</font></em> </p>
<p><font color="#400080"><em>StateServer需要在服务中开启&#8220;asp.net状态服务&#8221;的服务</em>。</font> </p>
<p><a title="http://www.imneio.com/2007/11/17/aspnetnote1/" href="http://www.imneio.com/2007/11/17/aspnetnote1/">http://www.imneio.com/2007/11/17/aspnetnote1/</a>，以上斜体内容摘自此链接 </p>
<h2>完全跨单点域登录</h2>
<p>完全跨域登录，是指A，B站点和P站点没有共同的父域，比如A站点为forum.yizhu1999.net,B站点为blog.yizhu1998.net，大家可以参考微软旗下的几个站点<a title="http://www.live.com" href="http://www.live.com/">http://www.live.com</a>，<a href="http://www.hotmail.com/">www.hotmail.com</a>,这两个站点就没有共同的父域，而仍然可以共用登录，怎样才能实现呢？请参考下图，由于这种情况ticket比较复杂，我们暂时把P站点创建的的ticket叫做P-ticket，而A站点创建的ticket叫A-ticket，B的为B-ticket</p>
<p><a href="http://images.cnblogs.com/cnblogs_com/yizhu2000/WindowsLiveWriter/3616c09558a3_136C8/login_4.gif"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="445" alt="login" src="http://images.cnblogs.com/cnblogs_com/yizhu2000/WindowsLiveWriter/3616c09558a3_136C8/login_thumb_1.gif" width="605" border="0" /></a> </p>
<p>&nbsp;</p>
<p>由于站点A（forum.yizhu1999.com）不能读取到由站点P（passport.yizhu2000.com）创建的加密ticket，所以当用户访问A站点上需要登录才能访问的资源时，A站点会首先查看是否有A-ticket，如果没有，证明用户没有在A站点登录过，不过并不保证用户没有在B站点登录，（重复一下，既然是单点登录，当然无论你在A，B任意一个站点登录过，另外一个站点都要可以访问），请求会被重定向到p站点的验证页面，验证页面读取P-ticket，如果没有，或者解密不成功，就需要重定向登录页面，登录页面完成登录后，写一个加密cookie，也就是P-ticket，并且重定向到A站点的登录处理页，并把加密的用户信息作为参数传递给这个页面，这个页面接收登录页的用户信息，解密后也要写一个cookie，也就是A-ticket，今后用户再次访问A站点上需要登录权限才能访问的资源时，只需要检查这个A-cookie是否存在就可以了</p>
<p>当用户访问B站点时，会重复上面的过程，监测到没有B-ticket，就会重定向到P站点的验证页面，去检查P-ticket，如果没有，就登录，有则返回B的登录处理页面写B-ticket</p>
<p>&nbsp;</p>
<p>注销的时候需要删除P-ticket和A-ticket</p>
<p>&nbsp;</p>
<p><a href="http://images.cnblogs.com/cnblogs_com/yizhu2000/WindowsLiveWriter/3616c09558a3_136C8/logout.gif"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="347" alt="logout" src="http://images.cnblogs.com/cnblogs_com/yizhu2000/WindowsLiveWriter/3616c09558a3_136C8/logout_thumb.gif" width="568" border="0" /></a> </p>
<p>&nbsp;</p>
<p>怎么删除cookie：本来以为这个不是问题，不过还是有朋友问道，简单的说其实是创建一个和你要删除的cookie同名的cookie，并把cookie的expire设为当前时间之前的某个时间，不过在跨子域的删除cookie时有一点要注意：必须要把cookie的域设置为父域，在本文中为yizhu2000.com</p>
<p>为了保证各个环节的传输的安全性，最好使用https连接</p></div></div> <img src ="http://www.blogjava.net/hao446tian/aggbug/361577.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hao446tian/" target="_blank">昊天</a> 2011-10-19 14:57 <a href="http://www.blogjava.net/hao446tian/archive/2011/10/19/361577.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>