`

手把手教你可复用的SSO组件设计(实现篇)

阅读更多

费了一夜的功夫写完这些代码,有些凌乱,望见谅。

首先是对加密解密的抽象,因为.NET对3DES的实现很龌龊,不支持String,所以我们必须把他封装为可以用String作为Key,IV,输入和输出的3DES加密类,这个很重要,对Cookie的加密和解密和对URL的加密解密都靠它了

 

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

namespace Alexander.SSOLib.Cryptography
{
public class Decrypter
{
private ICryptoTransform decrypter;

public Decrypter(string Key, string IV)
{
System.Security.Cryptography.TripleDES des = System.Security.Cryptography.TripleDESCryptoServiceProvider.Create();
des.Key = Common.Str2Byte(Key);
des.IV = Common.Str2Byte(IV);
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.PKCS7;
decrypter = des.CreateDecryptor();
}

public string DecryptString(string InputString)
{
byte[] input = Common.Str2Byte(InputString);
byte[] output = decrypter.TransformFinalBlock(input, 0, input.Length);
return Common.NormalByte2Str(output);
}
}
}

这里我们看看核心在这里

System.Security.Cryptography.TripleDES des = System.Security.Cryptography.TripleDESCryptoServiceProvider.Create();

得到了3des处理程序的实例,注意TripleDES是基类,这里是个典型Factory或者是Builder,看不到内部实现,猜的。

在这里要设置Key和IV才可以使用,让人无比郁闷的是,Key和IV都需要byte数组,所以不得不把输入的字符串转换一次,我们看到调用了Common类的静态方法。这里我们来看看这个Common类

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

namespace Alexander.SSOLib.Cryptography
{
public class Common
{
public static byte[] Str2Byte(string Str)
{
return Convert.FromBase64String(Str);
}
public static string Byte2Str(Byte[] Byt)
{
return Convert.ToBase64String(Byt);
}
public static Byte[] NormalStr2Byte(string str)
{
return Encoding.Default.GetBytes(str);
}
public static string NormalByte2Str(byte[] Byt)
{
return Encoding.Default.GetString(Byt);
}
}
}

这里有四个方法,分别是:

Convert.FromBase64String(Str);

Convert.ToBase64String(Byt);

Encoding.Default.GetBytes(str);

Encoding.Default.GetString(Byt);

这里分别对Base64编码的字符串,和默认字符集得字符串与Byte[]之间的转换方法,这里要用Str2Byte(string Str)这个方法,其他几个我们后面用

why用Base64呢?因为我们把Key和IV的Byte数组可以用Base64编码成字符串就成了5dI2LJtk/z/gIEa0XsN66lASdvNHBmUV这样子的字符串,如果使用其他方式就成了乱码了。

在设置了Key和IV后就是显示设定一下填充模式等属性,其实没什么必要。

然后通过

des.CreateDecryptor();创建一个转换器实例,ICryptoTransform接口所定义的。

通过decrypter.TransformFinalBlock的时候就能转换了,注意输入的字符串需要Base64转换,输出字符串需要当前系统字符集创建。

加密类的结构和解密类差不多,不过输入用当前字符集转换到byte数组,而输出用Base64的方法,和解密是反过来的。

然后是Common里面的数据结构与操作类

KeyManager 管理Key和IV的

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

namespace Alexander.SSOLib.Common
{
public class KeyManager
{
private static string storebase;

static KeyManager()
{
storebase = System.Configuration.ConfigurationSettings.AppSettings["StoreBase"];
}

public static void GetKeyBySiteID(string ID,out string Key,out string IV)
{
string path = (storebase.EndsWith("\\")?storebase:storebase+"\\") + ID + ".Key"
if (File.Exists(path))
{
using (StreamReader reader = new StreamReader(path))
{
Key=reader.ReadLine();
IV = reader.ReadLine();
reader.Close();
}
}
else
{
Key = ""
IV = ""
}
}
public static void UpdateKey(string SiteID)
{
string path = (storebase.EndsWith("\\") ? storebase : storebase + "\\") + SiteID + ".Key"
using (StreamWriter writer = new StreamWriter(path,false,Encoding.Default))
{
Alexander.SSOLib.Cryptography.KeyMaker km = new Alexander.SSOLib.Cryptography.KeyMaker();
writer.WriteLine(km.Key);
writer.WriteLine(km.Iv);
writer.Flush();
writer.Close();
}
}
public static void DeleteKey(string SiteID)
{
string path = (storebase.EndsWith("\\") ? storebase : storebase + "\\") + SiteID + ".Key"
File.Delete(path);
}
}
}

PSORequest表示PSO端的请求

using System;
using System.Collections.Generic;
using System.Text;
using Alexander.SSOLib.Cryptography;

namespace Alexander.SSOLib.Common
{
public class PSORequest
{
private string Key=System.Configuration.ConfigurationSettings.AppSettings["Key"];

private string IV=System.Configuration.ConfigurationSettings.AppSettings["IV"];

private string siteid;

public string Siteid
{
get { return siteid; }
set { siteid = value; }
}
private string createdate;

public string Createdate
{
get { return createdate; }
set { createdate = value; }
}
private string returnurl;

public string Returnurl
{
get { return returnurl; }
set { returnurl = value; }
}

public PSORequest()
{
siteid = System.Configuration.ConfigurationSettings.AppSettings["SiteID"];
createdate = DateTime.Now.ToString();
returnurl = System.Web.HttpContext.Current.Request.Url.ToString();
}
public PSORequest(string HashString)
{
string[] rls = HashString.Split('$');
string sKey;
string sIV;
KeyManager.GetKeyBySiteID(rls[0], out sKey, out sIV);
Decrypter de = new Decrypter(sKey, sIV);
string rs = de.DecryptString(rls[1]);
string[] sp = rs.Split('|');
siteid = rls[0];
createdate = sp[0];
returnurl = sp[1];
}
public string CreateHash()
{
Encrypter en = new Encrypter(Key, IV);
return siteid + "$" + en.EncryptString(createdate + "|" + Returnurl);
}

}
}

这里我们可以看见 请求的数据包括站点的ID和3DES加密后的会话时间和返回的地址。

public PSORequest()是PSO端实例化的构造器,创建一个空的请求,并且通过方法

CreateHash()来创建一个加密的字符串。

public PSORequest(string HashString)在SSO端用来解析PSO请求的格式,然后把值写入对象的变量内

SSOResponse类包含了SSO端向PSO发送的反馈URL的格式的类

using System;
using System.Collections.Generic;
using System.Text;
using Alexander.SSOLib.Cryptography;

namespace Alexander.SSOLib.Common
{
public class SSOResponse
{

private string Key = System.Configuration.ConfigurationSettings.AppSettings["Key"];

private string IV = System.Configuration.ConfigurationSettings.AppSettings["IV"];

private PSORequest request;

private string Response;
public SSOResponse(PSORequest Request)
{
request = Request;
}
public SSOResponse(string SSOResponse)
{
Response = SSOResponse;
}
public string CreateResponseString(Ticket ticket)
{
ticket.Createdate=request.Createdate;
string data = request.Siteid + "|" + ticket.Userid + "|" + ticket.Username + "|" + ticket.Ticketdata + "|" + ticket.Createdate;
string sKey;
string sIV;
KeyManager.GetKeyBySiteID(request.Siteid,out sKey,out sIV);
Encrypter enc = new Encrypter(sKey, sIV);
return enc.EncryptString(data);
}
public Ticket CreatePSOTicket()
{
Decrypter dc = new Decrypter(Key, IV);
string data = dc.DecryptString(Response.Trim().Replace(" ","+"));
string[] ls = data.Split('|');
Ticket tc = new Ticket(ls[0], ls[1], ls[2], ls[3]);
return tc;
}
}
}

同样的,有两个构造器,一个在PSO端用,一个在SSO端用。原理和PSORequest差不多。

Ticket类包含了Cookie的结构和加密解密,加载和保存的操作

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Alexander.SSOLib.Cryptography;

namespace Alexander.SSOLib.Common
{
public class Ticket
{
private Encrypter enc;
private Decrypter dec;
private string Key = System.Configuration.ConfigurationSettings.AppSettings["Key"];
private string IV = System.Configuration.ConfigurationSettings.AppSettings["IV"];

private string userid=""

public string Userid
{
get { return dec.DecryptString(userid); }
set { userid = enc.EncryptString(value); }
}
private string username=""

public string Username
{
get { return dec.DecryptString(username); }
set { username = enc.EncryptString(value); }
}
private string ticketdata=""

public string Ticketdata
{
get { return dec.DecryptString(ticketdata); }
set { ticketdata = enc.EncryptString(value); }
}

private string createdate=""

public string Createdate
{
get { return dec.DecryptString(createdate); }
set { createdate = enc.EncryptString(value); }
}

public Ticket(string UserID, string UserName, string TicketData, string CreateDate)
{
enc = new Encrypter(Key, IV);
dec = new Decrypter(Key, IV);
userid = enc.EncryptString(UserID);
username = enc.EncryptString(UserName);
ticketdata = enc.EncryptString(TicketData);
createdate = enc.EncryptString(CreateDate);
}
public Ticket()
{
enc = new Encrypter(Key, IV);
dec = new Decrypter(Key, IV);
}
public bool LoadTicket(string domain)
{
if (System.Web.HttpContext.Current.Request.Cookies[domain] != null)
{
userid = System.Web.HttpContext.Current.Request.Cookies[domain]["UserID"];
username = System.Web.HttpContext.Current.Request.Cookies[domain]["UserName"];
ticketdata = System.Web.HttpContext.Current.Request.Cookies[domain]["TicketDate"];
return true;
}
else
{
return false;
}
}
public void SaveTicket(string domain)
{
System.Web.HttpContext.Current.Response.Cookies[domain]["UserID"] = userid;
System.Web.HttpContext.Current.Response.Cookies[domain]["UserName"] = username;
System.Web.HttpContext.Current.Response.Cookies[domain]["TicketDate"] = ticketdata;
}
}
}

经过以上层次的抽象,于是我们来看看PSOClient

using System;
using System.Collections.Generic;
using System.Text;
using Alexander.SSOLib.Common;

namespace Alexander.SSOLib.PSO
{
public class PSOClient:System.Web.IHttpModule
{
#region IHttpModule 成员

System.Web.HttpApplication Context;

public void Dispose()
{
// throw new Exception("The method or operation is not implemented.");
}

public void Init(System.Web.HttpApplication context)
{
Context = context;
context.BeginRequest += new EventHandler(context_BeginRequest);
}

void context_BeginRequest(object sender, EventArgs e)
{
string ExceptList = "$$" + System.Configuration.ConfigurationSettings.AppSettings["Exceptlist"];

if (ExceptList.IndexOf(Context.Request.Url.ToString()) < 1)
{
string Key = System.Configuration.ConfigurationSettings.AppSettings["SSOKey"];
string ssoresponse = Context.Request.QueryString[Key];
SSOResponse sr = new SSOResponse(ssoresponse);
if (ssoresponse != null)
{
Ticket tc = sr.CreatePSOTicket();
tc.SaveTicket(System.Configuration.ConfigurationSettings.AppSettings["domain"]);
}
else
{
Ticket tc = new Ticket();
if (!tc.LoadTicket(System.Configuration.ConfigurationSettings.AppSettings["domain"]))
{
PSORequest request = new PSORequest();
string requeststr = request.CreateHash();
string KeeperUrl = System.Configuration.ConfigurationSettings.AppSettings["KeeperUrl"];
Context.Response.Redirect(KeeperUrl + "?" + Key + "=" + Context.Server.UrlEncode(requeststr));
}
}
}
}

#endregion
}
}

一个典型的HttpModule,逻辑清晰简单,很好懂

SSO也是差不多。

最后看看完整的代码:http://www.cnblogs.com/Files/Alexander-Lee/SSO.rar

分享到:
评论

相关推荐

    [有源代码]单点登陆(SSO)组件的设计与实现

    在提供的"单点登陆(SSO)组件的设计与实现.doc"文档中,可能会详细介绍SSO组件的架构设计、实现步骤、技术选型,以及如何将这些组件集成到实际应用中的具体细节。同时,"SSO.rar"可能是源代码包,包含了SSO组件的实现...

    C#单点登陆组件源码SSO

    本项目提供了一款基于C#编写的SSO组件源码,其设计思路类似于微软的认证服务器和Web应用服务器,能够实现跨域和跨服务器的身份验证。 首先,理解C#中的SSO实现原理至关重要。SSO的核心在于票据(Ticket)的概念,当...

    SSO个人实现方式

    SSO的实现通常涉及到以下几个关键组件: 1. **认证中心(Identity Provider, IDP)**:这是SSO的核心,负责用户的登录验证。在SSO系统中,用户只需向IDP提供用户名和密码。 2. **服务提供者(Service Provider, SP...

    CAS框架SSO的实现

    CAS框架SSO的实现

    SSO的简单实现SSO的简单实现

    以下是对SSO简单实现的详细说明: 在实现SSO时,有多种方法可以选择: 1. **商业软件**:一些大型公司如 Netgrity(Siteminder)、Novell(iChain)、RSA(ClearTrust)等提供专门的SSO解决方案。这些软件通常具备...

    java web sso 实现

    Java Web SSO(Single Sign...总之,Java Web SSO的实现涉及到多个组件和协议,需要对身份验证、会话管理以及相关开源技术有深入理解。开发者应关注安全性、用户体验和系统的可扩展性,以构建高效且可靠的SSO解决方案。

    SSO单点登录实现与实现原理

    SSO(Single Sign-On)单点登录是一种身份验证机制,允许用户在多个相互关联的应用系统中只需要登录一次,即可访问所有...了解SSO的实现原理和源码分析,对于IT从业者来说,无论是系统设计还是故障排查,都将大有裨益。

    【转载】CAS实现单点登录(SSO)经典完整教程

    CAS(Central Authentication Service)是一种广泛使用的开放源码的单点登录(Single ...在提供的文档《手把手教你利用CAS实现单点登录.docx》中,你将找到更详细的步骤和示例代码,帮助你更好地理解和实施CAS SSO。

    ssm redis实现sso单点登录

    在本文中,我们将深入探讨如何使用SSM(Spring MVC、Spring、MyBatis)框架结合Redis缓存来实现SSO功能。 首先,了解SSO的核心原理是关键。SSO主要依赖于票据(Ticket)的概念,当用户成功登录到一个系统时,服务器...

    java单点登录的实现与应用整合中SSO的技术实现

    Java单点登录(Single Sign-On, SSO)的实现是一个复杂但重要的任务,尤其是在大型企业或组织中,它能够提供用户友好的访问体验,减少多次输入凭证的繁琐过程。SSO技术的应用通常涉及到多系统间的身份验证整合,使得...

    SSO(单点登录)实战篇:客户端实现(1)视频

    SSO(单点登录)实战篇:客户端实现(1)1.Tomcat多应用部署的配置2.Maven项目的打包与发布3.客户端模块的主流程

    关于SSO单点登录的简单实现

    在本文中,我们将探讨SSO的基本原理、实现方式以及一个简单的SSO系统的设计。 **SSO基本原理** SSO的核心是通过共享一种信任关系来实现跨系统的身份验证。这种信任通常由一个中央认证服务(CAS,Central ...

    .net单点登录sso的实现

    二、.NET SSO实现的关键组件 1. 认证中心:负责用户的身份验证,为用户提供登录界面,并生成认证令牌。 2. 应用系统:需要与认证中心进行交互,验证用户的身份,并接受认证令牌。 3. 协议支持:例如CAS(Central ...

    spring boot 实现SSO单点登陆

    spring boot整合spring security 实现SSO单点登陆 完整DEMO. ...2、先后启动SsoServer、sso-resource、sso-client1、sso-client2 3、访问http://sso-taobao:8083/client1/ 或 http://sso-tmall:8084/client2/

    单点登录系统(SSO)技术实现剖析.PDF

    下面我们将深入剖析SSO技术的实现原理及其关键组成部分。 1. **SSO原理**: SSO的核心思想是用户只需在一个“认证中心”进行身份验证,之后便可以在所有信任该认证中心的应用系统中自由切换,无需再次输入凭证。这...

    CAS实现sso单点登录原理

    5. 提供高可用性:通过把认证过的状态数据存储在TicketRegistry组件中,这些组件有很多支持分布式环境的实现,如:BerkleyDB、Default、EhcacheTicketRegistry、JDBCTicketRegistry、JBOSS TreeCache、...

Global site tag (gtag.js) - Google Analytics