分享

轻量级单点登录系统最佳实践(上)

 CevenCheng 2010-12-23

轻量级单点登录系统最佳实践(一)——目录

  目录

  第1章 前言

  第2章 单点登录简介

  第3章 WEB-SSO通常实现方式

  第4章 轻量级单点登陆系统简介

  第5章 轻量级单点登陆系统实现

   5.1. 公共组件SSOLAB.SSOSERVER.COMPONENTS

   5.2. 单点登录系统SSOLAB.SSOSERVER.WEBAPP

   5.3. 企业门户系统系统演示SSOLAB.PORTAL.WEBAPP

   5.4. 人力资源管理系统演示SSOLAB. APP1. WEBAPP

   5.5.财务管理系统演示SSOLAB. APP2. WEBAPP

   5.6. 网上办公系统演示APP3 (JAVA)

  第6章 后记

  轻量级单点登录系统最佳实践(二)——第1章 前言

  要实现企业应用集成,就不能不解决单点登录问题。单点登录(SSO,Single Sign On) 也可称统一认证服务,就是用户只登录一次就可以访问多个应用系统而不需要重新登录。怎么解决单点登录问题,用任何一个搜索引擎,都可以找到各种解决方法,可谓是八仙过海、各显神通。本文的目的不是提供一个功能齐全、安全可靠的解决方法,而是提供一个只使用原始Web技术、与软件平台无关、与用户验证形式无关、只有用户验证功能、可以在安全性不过强求的情况下使用的方法——轻量级单点登录系统。虽然本文是使用.NET框架、C#实现的,但完全可以按照同样方法使用其它平台、其它语言来实现;虽然本文是使用用户名和密码写在代码中这种最烂的用户验证形式,但完全可以使用数据库、Active Directory等形式来验证用户。

  很多单点登录解决方法,不论是技术、还是思想,都非常复杂,不管是商业的、有代码的,方方面面都考虑到了。想必很多程序员和我一样,学习Web程序开发的第一步,就是解决用户登录问题,在互联网不发达的情况下,都颇费周折,实现单点登录也和这差不多,不过绕了个圈子罢了。复杂的单点登录方法,往往都附加了很多与单点登录无关的职责,刨去这些无关的职责,就只是在一个地方进行用户登录的很小的应用系统。

轻量级单点登录系统的特性:

  l 轻量级;

  l 基于Web-SSO方案;

  l 只是通过代码来说明方法,与开发平台、语言无关,与用户验证形式无关;

  l 不使用客户端Cookies,支持跨域认证;

  l 有基本的安全性;

  轻量级单点登录系统最佳实践(三)——第2章 单点登录简介

  目前的企业应用环境中,往往有很多的应用系统,如人力资源管理系统、办公自动化系统、财务管理系统、档案管理系统等等。这些应用系统服务于企业的信息化建设,为企业带来了很好的效益。但是,用户在使用这些应用系统时,并不方便。用户每次使用系统,都必须输入用户名称和用户密码,进行身份验证;而且,应用系统不同,用户账号就不同,用户必须同时牢记多套用户名称和用户密码。特别是对于应用系统数目较多,用户数目也很多的企业,这个问题尤为突出。问题的原因并不是系统开发出现失误,而是缺少整体规划,缺乏统一的用户登录平台。

  SSO(Single Sign On,单点登录)可以解决上述问题。所谓单点登录,就是是指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其他应用中的受保护资源时,不再需要重新登录验证。

  使用SSO的好处主要有:

  方便用户。用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。

  方便管理员。系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。

简化应用系统开发。开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

  单点登录实现机制比较简单,单点登录的实质就是安全上下文(Security Context)或凭证(Credential)在多个应用系统之间的传递或共享。

轻量级单点登录系统最佳实践(上)

  如上图所示,当用户第一次访问应用系统1的时候,因为还没有登,会被引导到认证系统中进行登录(1);根据用户提供的登录信息,认证系统进行身份效验,如果通过效验,应该返回给用户一个认证的凭据--ticket(2);用户再访问别的应用的时候(3,5)就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行效验,检查ticket的合法性(4,6)。如果通过效验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。

  要实现SSO,需要以下主要的功能:

  所有应用系统共享一个身份认证系统。统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行效验,判断其有效性。

  所有应用系统能够识别和提取ticket信息。要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录过,从而完成单点登录的功能。

 轻量级单点登录系统最佳实践(四)——第3章 Web-SSO通常实现方式

  随着互联网的高速发展,WEB应用几乎统治了绝大部分的软件应用系统,因此WEB-SSO是SSO应用当中最为流行。WEB-SSO有其自身的特点和优势,实现起来比较简单易用。

  众所周知,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的功能,需要两个部分的合作:

  l 统一的身份认证服务。

  l 修改Web应用,使得每个应用都通过这个统一的认证服务来进行身份效验。

  轻量级单点登录系统最佳实践(五)——第4章 轻量级单点登陆系统简介

  轻量级单点登录系统解决方案包括以下项目:

  l 公共组件SSOLab.SSOServer.Components

  l 单点登录系统SSOLab.SSOServer.WebApp

  l 企业门户系统系统演示SSOLab.Portal.WebApp

  l 人力资源管理系统演示SSOLab. APP1. WebApp

  l 财务管理系统演示SSOLab. APP2. WebApp

  l 网上办公系统演示App3 (Java)

  Visual Studio 2008解决方案图

轻量级单点登录系统最佳实践(上)

  Eclipse项目图

轻量级单点登录系统最佳实践(上)

  整个解决方案运行过程如下:

  1、访问企业门户系统http://localhost:7772/Portal/Default.aspx。

  由于用户还没有在单点登录系统上登录过,所以跳转到单点登录系统用户登录页面http://localhost:7771/SSOSite/SignIn.aspx

轻量级单点登录系统最佳实践(上)

  2、输入正确的用户名和密码,跳转到企业门户系统首页面http://localhost:7772/Portal/Default.aspx,显示当前登陆用户的用户名和应用系统地址

轻量级单点登录系统最佳实践(上)

  3.选择人力资源管理系统,打开人力资源管理系统首页面http://localhost:7773/App1/Default.aspx,显示当前登陆用户的用户名。

轻量级单点登录系统最佳实践(上)

  4.选择财务管理系统,打开财务管理系统首页面http://localhost:7774/App2/Default.aspx,显示当前登陆用户的用户名。

轻量级单点登录系统最佳实践(上)

  4.选择网上办公系统,打开网上办公系统首页面http://localhost:8080/App3/default.jsp,显示当前登陆用户的用户名。

轻量级单点登录系统最佳实践(上)

  轻量级单点登录系统最佳实践(六)——5.1. 公共组件SSOLab.SSOServer.Components

 l Application.cs应用系统类。属性:ID、名称、单点登录秘钥。

  l ApplicationService.cs应用系统服务类。方法:根据名称获取应用系统。为了叙述简便,直接把应用系统的信息写入代码中。

  l User.cs用户类。属性:ID、用户名、密码。

  l UserService.cs用户服务类。方法:验证用户、根据ID获取用户、根据用户名获取用户。为了叙述简便,直接把用户的信息写入代码中。

  l SSOUtil.cs单点登录工具类。静态方法:获取随机字符串、DES加密、DES解密、获取网站地址

  Application.cs


using System;
using System.Collections.Generic;
using System.Text;

namespace SSOLab.SSOServer.Components
{
    public class Application
    {
        private string _id;
        private string _name;
        private string _ssoKey;

        public string ID
        {
            get
            {
                return this._id;
            }
            set
            {
                this._id = value;
            }
        }

        public string Name
        {
            get
            {
                return this._name;
            }
            set
            {
                this._name = value;
            }
        }

        public string SSOKey
        {
            get
            {
                return this._ssoKey;
            }
            set
            {
                this._ssoKey = value;
            }
        }

        public Application()
        {

        }
    }
using System;
using System.Collections.Generic;
using System.Text;

namespace SSOLab.SSOServer.Components
{
    public class ApplicationService
    {
        public static readonly int SSO_KEY_LENGTH = 128;

        public Application GetApplicationByName(string name)
        {
            if (name == "portal")
            {
                Application application = new Application();
                application.ID = "C8288957-B6AA-4522-99FF-7D1E60509974";
                application.Name = "门户系统";
                application.SSOKey = "Xj1wD4DT7UicRVxOBJdjAg2AjErHkoEDlB9GqMJtYMwbfbnc9slagStcVt0Y3lY0XVKDnn6nO9cnCPDwM0tJU6iCBlWEoomDfjAjhobLurOxHR8ua8a25NGNQXQ1Q34X";

                return application;
            }
            else if (name == "app1")
            {
                Application application = new Application();
                application.ID = "1466B140-D840-430a-9598-619D6888E9DB";
                application.Name = "人力资源管理系统";
                application.SSOKey = "XD0cEmXD0IcmYD0gBmYE0OdnYE1jHnZE1USnZF3y3GYpm93Gjp2s2GSog32FfoDm2FZoaG2FZnmhpkVBlJXkWB1eGkWCqA2lWC7k2lXCw1dlXDMqolZr805InrQk4Ixq";

                return application;
            }
            else if (name == "app2")
            {
                Application application = new Application();
                application.ID = "2A5F9A65-7490-480e-836F-DF18608D617B";
                application.Name = "财务管理系统";
                application.SSOKey = "XBtyndN8yHpZCiM1eO9XtE1qii9Oey17CYosH8cM7nRnXBIBjdN811pZrtw1PfhcBDyq7S9OeHcGmWAR7ycM7aloXBCsXQhe10FgrBEwPfSndDZpGwxbL55ymWAmhycM";

                return application;
            }
            else if (name == "app3")
            {
                Application application = new Application();
                application.ID = "F788FED3-32EE-470a-8DDF-0E6AD8A2FCEC";
                application.Name = "网上办公系统";
                application.SSOKey = "XJbbaaAnnQC67829OLkEKwgwiZL30oegpTbptQG0SLQG97665k4O32bb5CQdnffggufXJmBW16nZesssc2AOJl6bO0wiZLiu7k7FTbq27d0CdUG9110ykINvggh5CRjn";

                return application;
            }
            else
            {
                return null;
            }
        }
    }
}

User.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace SSOLab.SSOServer.Components
{
    public class User
    {
        private string _id;
        private string _username;
        private string _password;

        public string ID
        {
            get
            {
                return this._id;
            }
            set
            {
                this._id = value;
            }
        }

        public string Username
        {
            get
            {
                return this._username;
            }
            set
            {
                this._username = value;
            }
        }

        public string Password
        {
            get
            {
                return this._password;
            }
            set
            {
                this._password = value;
            }
        }

        public User()
        {

        }
    }
}

 UserService.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace SSOLab.SSOServer.Components
{
    public class UserService
    {
        public bool AuthenticationUser(string username, string password, out string id)
        {
            User user = GetUserByName(username);

            if (user != null && user.Password == password)
            {
                id = user.ID;

                return true;
            }
            else
            {
                id = String.Empty;

                return false;
            }
        }

        public User GetUserByID(string userID)
        {
            if (userID == "464FA65A-0DFF-46a9-AC0B-3EF1E4CDFF94")
            {
                User user = new User();
                user.ID = "464FA65A-0DFF-46a9-AC0B-3EF1E4CDFF94";
                user.Username = "admin";
                user.Password = "admin";

                return user;
            }
            else
            {
                return null;
            }
        }

        public User GetUserByName(string username)
        {
            if (username == "admin")
            {
                User user = new User();
                user.ID = "464FA65A-0DFF-46a9-AC0B-3EF1E4CDFF94";
                user.Username = "admin";
                user.Password = "admin";

                return user;
            }
            else
            {
                return null;
            }
        }
 }

SSOUtil.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;

namespace SSOLab.SSOServer.Components
{
    public class SSOUtil
    {
        public static string GetRandomString(int length)
        {
            StringBuilder sb = new StringBuilder(length);

            for (int i = 0; i < length; i++)
            {
                Random random = new Random(unchecked(i * (int)(DateTime.Now.Ticks)));

                int ret = random.Next(122);

                while (ret < 48 || (ret > 57 && ret < 65) || (ret > 90 && ret < 97))
                {
                    ret = random.Next(122);
                }

                sb.Append((char)ret);
            }
            return sb.ToString();

        }
       
        public static string DESEncrypt(string text, string key)
        {
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
            des.Mode = System.Security.Cryptography.CipherMode.ECB;
            des.Padding = PaddingMode.Zeros;
            des.Key = ASCIIEncoding.ASCII.GetBytes(key);

            byte[] inputBuffer = Encoding.GetEncoding("UTF-8").GetBytes(text);
            byte[] outputBuffer = des.CreateEncryptor().TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);

            return Convert.ToBase64String(outputBuffer);
        }

        public static string DESDecrypt(string text, string key)
        {

            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
            des.Mode = System.Security.Cryptography.CipherMode.ECB;
            des.Padding = PaddingMode.Zeros;
            des.Key = ASCIIEncoding.ASCII.GetBytes(key);

            byte[] inputBuffer = Convert.FromBase64String(text);
            byte[] outputBuffer = des.CreateDecryptor().TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);

            return Encoding.GetEncoding("UTF-8").GetString(outputBuffer);
        }

        public static string GetSiteUrl()
        {
            string path = HttpContext.Current.Request.ApplicationPath;
            if (path.EndsWith("/") && path.Length == 1)
            {
                return GetHostUrl();
            }
            else
            {
                return GetHostUrl() + path;
            }
        }

        public static string GetHostUrl()
        {
            return string.Format("{0}://{1}:{2}",
                HttpContext.Current.Request.Url.Scheme,
                HttpContext.Current.Request.Url.Host,
                HttpContext.Current.Request.Url.Port);
        }
    }
}

轻量级单点登录系统最佳实践(七)——5.2. 单点登录系统SSOLab.SSOServer.WebApp

  l SignIn.aspx单点登录系统登录页面。

  l SignIn.aspx.cs单点登录系统登录页面后台代码。用户登录成功后,返回应用系统相应页面。

  l SignOut.aspx单点登录系统注销页面。

  l SignOut.aspx.cs单点登录系统注销页面后台代码。

  l SSOContext.aspx单点登录系统上下文页面。

  l SSOContext.aspx.cs单点登录系统上下文页面后台代码。根据应用系统请求返回相应信息,其中用户信息为加密形式,每个应用系统采用不同的秘钥。

  SignIn.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SignIn.aspx.cs" Inherits="SSOLab.SSOServer.WebApp.SignIn" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www./1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table width="300">
            <tr>
                <td>
                    用户名
                </td>
                <td>
                    <asp:TextBox ID="txtUsername" runat="server" />
                </td>
            </tr>
            <tr>
                <td>
                    密码
                </td>
                <td>
                    <asp:TextBox ID="txtPassword" runat="server" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Button ID="btnSignIn" runat="server" OnClick="btnSignIn_Click" Text="登录" />
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

 SignIn.aspx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;
using SSOLab.SSOServer.Components;

namespace SSOLab.SSOServer.WebApp
{
    public partial class SignIn : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void btnSignIn_Click(object sender, EventArgs e)
        {
            string userID;

            bool passed = new UserService().AuthenticationUser(txtUsername.Text,
                txtPassword.Text,
                out userID);

            if (passed && !String.IsNullOrEmpty(userID))
            {
                FormsAuthentication.SetAuthCookie(userID, true);
                Session["USER_IS_LONGIN"] = true;
                Session["USER_ID"] = userID;

                string returnUrl = HttpUtility.UrlDecode(Request.Params["ReturnUrl"]);
                Response.Redirect(returnUrl);
            }
            else
            {
                Session["USER_IS_LONGIN"] = false;
                Session["USER_ID"] = String.Empty;

                Response.Write("登录失败!");
            }
        }
    }
}

  SignOut.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SignOut.aspx.cs" Inherits="SSOLab.SSOServer.WebApp.SignOut" %>

  SignOut.aspx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace SSOLab.SSOServer.WebApp
{
    public partial class SignOut : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            FormsAuthentication.SignOut();

            Session.Remove("USER_IS_LONGIN");
            Session.Remove("USER_ID");

            string returnUrl = HttpUtility.UrlDecode(Request.Params["ReturnUrl"]);
            Response.Redirect(returnUrl);
        }
    }
}

  SSOContext.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SSOContext.aspx.cs" Inherits="SSOLab.SSOServer.WebApp.SSOContext1" %>

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多