最近在做一個(gè)無(wú)敵長(zhǎng)的項(xiàng)目,從五一休假做到十一休假!有一個(gè)需求就是要求單點(diǎn)登陸(SSO)
解決思路如下:
請(qǐng)求認(rèn)證的網(wǎng)站 :用一個(gè)HttpModule 截取所有請(qǐng)求,判斷HttpContext.User是不是Null,如為空,判斷Url上是不是有簽名過(guò)的認(rèn)證信息, 如果有,判斷簽名信息是否有效,如果有效,將認(rèn)證信息寫(xiě)入Cookie中.認(rèn)證完成
認(rèn)證的網(wǎng)站: 如果登陸頁(yè)Url中有要求認(rèn)證的網(wǎng)址,判斷用戶(hù)是不是已授權(quán),如果已授權(quán),將用戶(hù)信息簽名,寫(xiě)入U(xiǎn)rl中
二個(gè)網(wǎng)站都使用的Form認(rèn)證
代碼
請(qǐng)求認(rèn)證網(wǎng)站,HttpMoudle如下
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using System.Web;
5
using System.Web.Security;
6
using System.Security.Principal;
7
8
namespace SSO
9

{
10
/**//// <summary>
11
/// 單點(diǎn)認(rèn)證的HttpModule
12
/// </summary>
13
public class SSOAuthenticateHttpModule:IHttpModule
14
{
15
public String ModuleName
16
{
17
get
{ return "SSOAuthenticateHttpModule"; }
18
}
19
20
IHttpModule 成員#region IHttpModule 成員
21
22
public void Dispose()
23
{
24
25
}
26
27
public void Init(HttpApplication context)
28
{
29
context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
30
context.BeginRequest += new EventHandler(context_BeginRequest);
31
}
32
33
void context_BeginRequest(object sender, EventArgs e)
34
{
35
HttpApplication application = (HttpApplication)sender;
36
37
HttpContext context = application.Context;
38
39
HttpResponse Response = context.Response;
40
Response.AddHeader("P3P", "CP=CAO PSA OUR");//加上這個(gè),防止在Iframe的時(shí)間Cookie丟失
41
}
42
43
44
45
void context_AuthenticateRequest(object sender, EventArgs e)
46
{
47
48
49
HttpApplication application = (HttpApplication)sender;
50
51
HttpContext context = application.Context;
52
53
HttpRequest Request = context.Request;
54
HttpResponse Response = context.Response;
55
56
//如果不是網(wǎng)頁(yè),就不管他了
57
if(!Request.Url.AbsolutePath.EndsWith(".aspx",StringComparison.OrdinalIgnoreCase))
58
return ;
59
60
//好像加不加都行
61
FormsAuthentication.Initialize();
62
63
//如果當(dāng)前用戶(hù)為空
64
if (context.User == null)
65
{
66
//s表示簽名后的信息
67
String s = Request.QueryString["s"];
68
//表示真正要傳的信息 如果需要,可以把此部分信息也加密
69
String v = application.Server.UrlDecode(Request.QueryString["v"]);
70
71
72
if (!String.IsNullOrEmpty(s) && !String.IsNullOrEmpty(v))
73
{
74
//UrlDecode會(huì)把 + 號(hào)變成 '' 不知道為啥,這里再換回來(lái),
75
s = s.Replace(' ', '+');
76
77
通過(guò)之前存的Cookie判斷是不是最近的驗(yàn)證信息,防止別人Copy Url 地址#region 通過(guò)之前存的Cookie判斷是不是最近的驗(yàn)證信息,防止別人Copy Url 地址
78
HttpCookie ticksCookie = application.Request.Cookies["Ticks"];
79
String AuthTicks = String.Empty;
80
81
if (ticksCookie != null)
82
{
83
AuthTicks = ticksCookie.Value;
84
}
85
//加到認(rèn)證信上,簽名過(guò)的信息包含此內(nèi)容,只是在V中沒(méi)有傳過(guò)來(lái)
86
v = v + "$" + AuthTicks;
87
#endregion
88
89
//判斷簽名
90
if (SSOClient.ValidataData(v, s))
91
{
92
//取出用戶(hù)
93
String userName = SSOClient.SplitUserName(v);
94
//Token信息
95
String tokenValue = SSOClient.SplitToken(v);
96
97
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
98
1, // Ticket version
99
userName, // Username associated with ticket
100
DateTime.Now, // Date/time issued
101
DateTime.Now.AddMinutes(30), // Date/time to expire
102
false, // "true" for a persistent user cookie
103
tokenValue , // User-data, in this case the roles
104
FormsAuthentication.FormsCookiePath);// Path cookie valid for
105
106
107
//寫(xiě)入自己的Cookie和用戶(hù)信息
108
context.User = new GenericPrincipal(new FormsIdentity(ticket), new String[1]);
109
SSOClient.SetAuthCookie(context, userName, tokenValue, false);
110
111
}
112
else
113
{
114
//簽名無(wú)效,重定向到登陸頁(yè)
115
Response.Redirect(FormsAuthentication.LoginUrl);
116
}
117
}
118
else
119
{
120
//在這里存一個(gè)Cookie信息,在驗(yàn)證后,由Server傳回,以判斷是不是自己發(fā)出的請(qǐng)求
121
String ticks = System.DateTime.Now.Ticks.ToString();
122
HttpCookie cookie = new HttpCookie("Ticks",ticks);
123
124
application.Response.Cookies.Add(cookie);
125
126
//請(qǐng)服務(wù)端認(rèn)證
127
Response.Redirect(FormsAuthentication.LoginUrl + "?site=" + HttpUtility.UrlEncode(Request.Url.ToString()) + "&ticks=" + ticks);
128
}
129
130
}
131
132
}
133
134
#endregion
135
}
136
}
137
SSOClient 是一個(gè)助手類(lèi)主要負(fù)責(zé)認(rèn)證簽名,設(shè)置Cookie,從Url中分離出認(rèn)證信息
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using System.Security.Cryptography;
5
using System.Security.Principal;
6
using System.Web;
7
using System.Web.Security;
8
9
10
namespace SSO
11

{
12
internal class SSOClient
13
{
14
private static String PublicKey = "公鑰信息,我用的是RSA,你可以自己生成"
15
internal static bool ValidataData(String data, String signedData)
16
{
17
try
18
{
19
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(512);
20
RSA.FromXmlString(PublicKey);
21
22
UnicodeEncoding ByteConverter = new UnicodeEncoding();
23
24
SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
25
return RSA.VerifyData(ByteConverter.GetBytes(data), new SHA1CryptoServiceProvider(), Convert.FromBase64String(signedData));
26
}
27
catch
28
{
29
return false;
30
}
31
32
}
33
34
internal static String SplitUserName(String data)
35
{
36
UnicodeEncoding ByteConverter = new UnicodeEncoding();
37
38
return data.Split('$')[0];
39
}
40
41
42
internal static String SplitToken(String data)
43
{
44
UnicodeEncoding ByteConverter = new UnicodeEncoding();
45
46
47
return data.Split('$')[1];
48
}
49
50
internal static void SetAuthCookie(HttpContext context, String userName, String token, bool isPersistent)
51
{
52
53
54
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
55
1, // Ticket version
56
userName, // Username associated with ticket
57
DateTime.Now, // Date/time issued
58
DateTime.Now.AddMinutes(30), // Date/time to expire
59
isPersistent, // "true" for a persistent user cookie
60
token, // User-data, in this case the roles
61
FormsAuthentication.FormsCookiePath);// Path cookie valid for
62
63
// Encrypt the cookie using the machine key for secure transport
64
string hash = FormsAuthentication.Encrypt(ticket);
65
66
HttpCookie cookie = new HttpCookie(
67
FormsAuthentication.FormsCookieName, // Name of auth cookie
68
hash); // Hashed ticket
69
70
// Set the cookie's expiration time to the tickets expiration time
71
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
72
73
// Add the cookie to the list for outgoing response
74
context.Response.Cookies.Add(cookie);
75
}
76
77
78
}
79
}
80
被認(rèn)證的網(wǎng)站的WebConfig文件
<?xml version="1.0"?>
<!--
注意: 除了手動(dòng)編輯此文件以外,您還可以使用
Web 管理工具來(lái)配置應(yīng)用程序的設(shè)置。可以使用 Visual Studio 中的
“網(wǎng)站”->“Asp.Net 配置”選項(xiàng)。
設(shè)置和注釋的完整列表在
machine.config.comments 中,該文件通常位于
\Windows\Microsoft.Net\Framework\v2.x\Config 中
-->
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<!--
設(shè)置 compilation debug="true" 將調(diào)試符號(hào)插入
已編譯的頁(yè)面中。但由于這會(huì)
影響性能,因此只在開(kāi)發(fā)過(guò)程中將此值
設(shè)置為 true。
-->
<compilation debug="true">
<assemblies>
<add assembly="System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/></assemblies></compilation>
<!--
通過(guò) <authentication> 節(jié)可以配置 ASP.NET 使用的
安全身份驗(yàn)證模式,
以標(biāo)識(shí)傳入的用戶(hù)。
-->
<authentication mode="Forms">
<forms loginUrl="http://localhost/TestSSOServer/Default.aspx" name=".ASPXFORMSAUTH" protection="All" path="/" timeout="30" enableCrossAppRedirects="true"/>
</authentication>
<!--<authorization>
<deny users="?"/>
</authorization>-->
<httpModules>
<add name="SSOAuthenticateHttpModule" type="SSO.SSOAuthenticateHttpModule"/>
</httpModules>
<!--
如果在執(zhí)行請(qǐng)求的過(guò)程中出現(xiàn)未處理的錯(cuò)誤,
則通過(guò) <customErrors> 節(jié)可以配置相應(yīng)的處理步驟。具體說(shuō)來(lái),
開(kāi)發(fā)人員通過(guò)該節(jié)可以配置
要顯示的 html 錯(cuò)誤頁(yè)
以代替錯(cuò)誤堆棧跟蹤。

<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
-->
</system.web>
</configuration>

認(rèn)證網(wǎng)站,比較簡(jiǎn)單,有一個(gè)負(fù)責(zé)登陸的頁(yè)面,我就在上面放了一個(gè)Button和一個(gè)TxtBox
1
using System;
2
using System.Data;
3
using System.Configuration;
4
using System.Web;
5
using System.Web.Security;
6
using System.Web.UI;
7
using System.Web.UI.WebControls;
8
using System.Web.UI.WebControls.WebParts;
9
using System.Web.UI.HtmlControls;
10
11
public partial class _Default : System.Web.UI.Page
12

{
13
protected void Page_Load(object sender, EventArgs e)
14
{
15
if (!Page.IsPostBack)
16
{
17
//判斷是不是已認(rèn)證通過(guò)--這里只是簡(jiǎn)單寫(xiě)了,具體你是怎么認(rèn)證的,就怎么寫(xiě)
18
if (Request.IsAuthenticated)
19
{
20
//回到請(qǐng)求認(rèn)證的網(wǎng)站;
21
ReturnUrl();
22
}
23
24
}
25
26
}
27
protected void Button1_Click(object sender, EventArgs e)
28
{
29
FormsAuthentication.SetAuthCookie(TextBox1.Text,false);
30
31
ReturnUrl();
32
33
}
34
35
/**//// <summary>
36
/// 取得認(rèn)證信息
37
/// </summary>
38
/// <returns></returns>
39
private String getAuthInfo()
40
{
41
String userName = User.Identity.Name;
42
String tokenValue = "這是一些附加信息,你可以寫(xiě)入角色什么的";//在我的應(yīng)用中,我寫(xiě)的是一個(gè)Token
43
String time = System.DateTime.Now.ToString();
44
45
String v = userName + "$" + tokenValue ;
46
47
return v;
48
49
50
}
51
52
/**//// <summary>
53
/// 返回請(qǐng)求認(rèn)證的網(wǎng)站
54
/// </summary>
55
private void ReturnUrl()
56
{
57
58
String strUrl = Request.QueryString["site"];
59
60
if (String.IsNullOrEmpty(strUrl))
61
{
62
return;
63
}
64
65
strUrl = HttpUtility.UrlDecode(strUrl);
66
67
//取得認(rèn)證信息
68
String data = getAuthInfo();
69
70
寫(xiě)入簽名信息#region 寫(xiě)入簽名信息
71
if (strUrl.IndexOf('?') == -1)
72
{
73
74
strUrl = strUrl + "?s=" + SSO.SSOServer.SignatueData(data + "$" + Request.QueryString["ticks"]);
75
strUrl = strUrl + "&v=" + Server.UrlEncode(data);
76
77
}
78
else
79
{
80
strUrl = strUrl + "&s=" + SSO.SSOServer.SignatueData(data + "$" + Request.QueryString["ticks"]);
81
strUrl = strUrl + "&v=" + Server.UrlEncode(data);
82
}
83
#endregion
84
85
Response.Redirect(strUrl);
86
87
88
}
89
}
90
認(rèn)證助手類(lèi)
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using System.Security.Cryptography;
5
6
namespace SSO
7

{
8
public class SSOServer
9
{
10
private static String PrivateKey = "私鑰信息,這個(gè)很重要,";
11
12
/**//// <summary>
13
/// 簽 名用戶(hù)信息
14
/// </summary>
15
/// <param name="data"></param>
16
/// <returns></returns>
17
public static String SignatueData(String data)
18
{
19
try
20
{
21
//Create a UnicodeEncoder to convert between byte array and string.
22
UnicodeEncoding ByteConverter = new UnicodeEncoding();
23
24
byte[] dataToSignatue = ByteConverter.GetBytes(data);
25
26
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(512);
27
RSA.FromXmlString(PrivateKey);
28
29
String SignadString = Convert.ToBase64String(RSA.SignData(dataToSignatue, new SHA1CryptoServiceProvider()));
30
return SignadString;
31
32
}
33
catch
34
{
35
return String.Empty;
36
}
37
}
38
//把一個(gè)字符串Base64編碼
39
public static String Base64String(String data)
40
{
41
try
42
{
43
//Create a UnicodeEncoder to convert between byte array and string.
44
UnicodeEncoding ByteConverter = new UnicodeEncoding();
45
46
byte[] dataToBase = ByteConverter.GetBytes(data);
47
48
return Convert.ToBase64String(dataToBase);
49
}
50
catch
51
{
52
return String.Empty;
53
}
54
55
}
56
57
}
58
}
59