SSO单点登录解决方案讨论三(转载自百洋软件研究实验)
文章出自于: 百洋软件研究实验室博客??http://www.po-soft.com
?3 WEB-SSO的实现
??????随着互联网的高速发展,WEB应用几乎统治了绝大部分的软件应用系统,因此WEB-SSO是SSO应用当中最为流行。WEB-SSO有其自身的特点和优 势,实现起来比较简单易用。很多商业软件和开源软件都有对WEB-SSO的实现。其中值得一提的是OpenSSO (https://opensso.dev.java.net),为用Java实现WEB-SSO提供架构指南和服务指南,为用户自己来实现WEB-SSO提供了理论的依据和实现的方法。??
??????为什么说WEB-SSO比较容易实现呢?这是有WEB应用自身的特点决定的。
??????众所周知,Web协议(也就是HTTP)是一个无状态的协议。一个Web应用由很多个Web页面组成,每个页面都有唯一的URL来定义。用户在浏览器的地 址栏输入页面的URL,浏览器就会向Web?Server去发送请求。如下图,浏览器向Web服务器发送了两个请求,申请了两个页面。这两个页面的请求是分别使用了两个单独的HTTP连接。所谓无状 态的协议也就是表现在这里,浏览器和Web服务器会在第一个请求完成以后关闭连接通道,在第二个请求的时候重新建立连接。Web服务器并不区分哪个请求来 自哪个客户端,对所有的请求都一视同仁,都是单独的连接。这样的方式大大区别于传统的(Client/Server)C/S结构,在那样的应用中,客户端 和服务器端会建立一个长时间的专用的连接通道。正是因为有了无状态的特性,每个连接资源能够很快被其他客户端所重用,一台Web服务器才能够同时服务于成 千上万的客户端。
?
??????但是我们通常的应用是有状态的。先不用提不同应用之间的SSO,在同一个应用中也需要保存用户的登录身份信息。例如用户在访问页面1的时候进行了登录,但 是刚才也提到,客户端的每个请求都是单独的连接,当客户再次访问页面2的时候,如何才能告诉Web服务器,客户刚才已经登录过了呢?浏览器和服务器之间有 约定:通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串,并且可以保存在浏览器中。如下图所示,当浏览器访问了 页面1时,web服务器设置了一个cookie,并将这个cookie和页面1一起返回给浏览器,浏览器接到cookie之后,就会保存起来,在它访问页 面2的时候会把这个cookie也带上,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状 态。
?
??????Web-SSO完全可以利用Cookie结束来完成用户登录信息的保存,将浏览器中的Cookie和上文中的Ticket结合起来,完成SSO的功能。
??????为了完成一个简单的SSO的功能,需要两个部分的合作:
???3.1 Web SSO 的样例
??????根据上面的原理,我用J2EE的技术(JSP和Servlet)完成了一个具有Web-SSO的简单样例。样例包含一个身份认证的服务器和两个简单的 Web应用,使得这两个 Web应用通过统一的身份认证服务来完成Web-SSO的功能。此样例所有的源代码和二进制代码都可以从网站地址http://gceclub.sun.com.cn/wangyu/下载。
??????样例下载、安装部署和运行指南:
<init-param>
<param-name>SSOServiceURL</param-name>
<param-value>http://wangyu.prc.sun.com:8080/SSOAuth/SSOAuth</param-value>
</init-param>
<init-param>
<param-name>SSOLoginPage</param-name>
<param-value>http://wangyu.prc.sun.com:8080/SSOAuth/login.jsp</param-value>
</init-param>
???3.2 WEB-SSO代码讲解
??????3.2.1身份认证服务代码解析
??????Web-SSO的源代码可以从网站地址http://gceclub.sun.com.cn/wangyu/web-sso/websso_src.zip下 载。身份认证服务是一个标准的web应用,包括一个名为SSOAuth的Servlet,一个login.jsp文件和一个failed.html。身份 认证的所有服务几乎都由SSOAuth的Servlet来实现了;login.jsp用来显示登录的页面(如果发现用户还没有登录过); failed.html是用来显示登录失败的信息(如果用户的用户名和密码与信息数据库中的不一样)。
??
??????SSOAuth的代码如下面的列表显示,结构非常简单,先看看这个Servlet的主体部分:
package DesktopSSO;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SSOAuth extends HttpServlet {
????
??????static private ConcurrentMap accounts;
??????static private ConcurrentMap SSOIDs;
??????String cookiename="WangYuDesktopSSOID";
??????String domainname;
????
??????public void init(ServletConfig config) throws ServletException {
??????????super.init(config);
??????????domainname= config.getInitParameter("domainname");
??????????cookiename = config.getInitParameter("cookiename");
??????????SSOIDs = new ConcurrentHashMap();
??????????accounts=new ConcurrentHashMap();
??????????accounts.put("wangyu", "wangyu");
??????????accounts.put("paul", "paul");
??????????accounts.put("carol", "carol");
??????}
??????protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
??????????PrintWriter ut = response.getWriter();
??????????String action = request.getParameter("action");
??????????String result="failed";
??????????if (action==null) {
??????????????handlerFromLogin(request,response);
??????????} else if (action.equals("authcookie")){
??????????????String myCookie = request.getParameter("cookiename");
??????????????if (myCookie != null) result = authCookie(myCookie);
??????????????out.print(result);
??????????????out.close();
??????????} else if (action.equals("authuser")) {
?????????????result=authNameAndPasswd(request,response);
??????????????out.print(result);
??????????????out.close();
??????????} else if (action.equals("logout")) {
??????????????String myCookie = request.getParameter("cookiename");
??????????????logout(myCookie);
??????????????out.close();
??????????}
??????}
.....
}
??????从代码很容易看出,SSOAuth就是一个简单的Servlet。其中有两个静态成员变量:accounts和SSOIDs,这两个成员变量都使用了 JDK1.5中线程安全的MAP类: ConcurrentMap,所以这个样例一定要JDK1.5才能运行。Accounts用来存放用户的用户名和密码,在init()的方法中可以看到我 给系统添加了三个合法的用户。在实际应用中,accounts应该是去数据库中或LDAP中获得,为了简单起见,在本样例中我使用了 ConcurrentMap在内存中用程序创建了三个用户。而SSOIDs保存了在用户成功的登录后所产生的cookie和用户名的对应关系。它的功能显 而易见:当用户成功登录以后,再次访问别的系统,为了鉴别这个用户请求所带的cookie的有效性,需要到SSOIDs中检查这样的映射关系是否存在。
??????在主要的请求处理方法processRequest()中,可以很清楚的看到SSOAuth的所有功能。
??????下面看看几个主要的功能函数:
private void handlerFromLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
??????????String username = request.getParameter("username");
??????????String password = request.getParameter("password");
??????????String pass = (String)accounts.get(username);
??????????if ((pass==null)||(!pass.equals(password)))
??????????????getServletContext().getRequestDispatcher("/failed.html").forward(request, response);
??????????else {
??????????????String gotoURL = request.getParameter("goto");
??????????????String newID = createUID();
??????????????SSOIDs.put(newID, username);
??????????????Cookie wangyu = new Cookie(cookiename, newID);
??????????????wangyu.setDomain(domainname);
??????????????wangyu.setMaxAge(60000);
??????????????wangyu.setValue(newID);
??????????????wangyu.setPath("/");
??????????????response.addCookie(wangyu);
??????????????System.out.println("login success, goto back url:" + gotoURL);
??????????????if (gotoURL != null) {
??????????????????PrintWriter ut = response.getWriter();
??????????????????response.sendRedirect(gotoURL);
??????????????????out.close();
??????????????}
??????????}???
??????}
??????handlerFromLogin()这个方法是用来处理来自login.jsp的登录请求。它的逻辑很简单:将用户输入的用户名和密码与预先设定好的用 户集合(存放在accounts中)相比较,如果用户名或密码不匹配的话,则返回登录失败的页面(failed.html),如果登录成功的话,需要为用 户当前的session创建一个新的ID,并将这个ID和用户名的映射关系存放到SSOIDs中,最后还要将这个ID设置为浏览器能够保存的cookie 值。
??????登录成功后,浏览器会到哪个页面呢?那我们回顾一下我们是如何使用身份认证服务的。一般来说我们不会直接访问身份服务的任何URL,包括 login.jsp。身份服务是用来保护其他应用服务的,用户一般在访问一个受SSOAuth保护的Web应用的某个URL时,当前这个应用会发现当前的 用户还没有登录,便强制将也页面转向SSOAuth的login.jsp,让用户登录。如果登录成功后,应该自动的将用户的浏览器指向用户最初想访问的那 个URL。在handlerFromLogin()这个方法中,我们通过接收“goto”这个参数来保存用户最初访问的URL,成功后便重新定向到这个页 面中。
??????另外一个要说明的是,在设置cookie的时候,我使用了一个setMaxAge(6000)的方法。这个方法是用来设置cookie的有效期,单位是 秒。如果不使用这个方法或者参数为负数的话,当浏览器关闭的时候,这个cookie就失效了。在这里我给了很大的值(1000分钟),导致的行为是:当你 关闭浏览器(或者关机),下次再打开浏览器访问刚才的应用,只要在1000分钟之内,就不需要再登录了。我这样做是下面要介绍的桌面SSO中所需要的功 能。
??????其他的方法更加简单,这里就不多解释了。