手把手教你可复用的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也是差不多。

最后看看完整的代码:https://files.cnblogs.com/Alexander-Lee/SSO.rar

posted on 2007-01-29 22:30  亚历山大同志  阅读(7891)  评论(25编辑  收藏  举报

导航