使用 ASP.NET 实现通行证安全身份验证(单点登录)






3.38/5 (8投票s)
2004 年 9 月 9 日
4分钟阅读

148888

1384
通行证身份验证允许用户登录到其内部网并访问其他 Web 应用程序,而无需再次要求登录。
引言
当我们需要一个 Web 应用程序供客户的用户(客户)使用时,出现了这个需求。他们已经登录到他们的网络并经过了网络身份验证。客户的内部网会有一个链接指向该 Web 应用程序。当他们点击链接时,可能会要求输入用户名和密码。该 Web 应用程序有自己的登录/密码页面,如果您没有通过该应用程序的网络域(Active Directory 角色)进行身份验证。但客户不喜欢这样,因为他们需要记住四五个不同的用户名/密码来访问他们需要从内部网使用的每个提供商应用程序。他们希望在点击内部网链接时访问该应用程序。当他们从内部网外部访问同一网站时,应该会提示登录。
可用解决方案
- 使用 Passport 身份验证(同样,客户用户需要创建 passport 帐户),并且 Web 应用程序应在 passport 中注册以使用其服务。
- 使用 SAML 身份验证模块,该模块允许联合安全身份验证。这需要大量工作。
- 使用安全密钥提供程序模块,该模块生成一个 Ticket,与来自客户端的用户凭据一起发送。Web 应用程序将使用相同的提供程序模块来验证密钥并进行登录身份验证。
- 使用 HTTP POST 将身份值以及哈希值(凭据 + 私钥)发送到 Web 应用程序。应用程序使用凭据和私钥创建哈希值,并匹配传递的哈希值,从而进行登录尝试身份验证。
解决方案
我选择第四种解决方案,因为它成本最低。它可能不是最好的,但在 SSL 连接上效果很好。但是,该解决方案需要在客户端进行一些工作来传递值。客户端代码将通过 POST 将四个参数传递到应用程序的通行证页面:客户端 ID、用户名、时间戳和密钥哈希。客户端 ID 是一个用于标识客户端(在 Web 应用程序中有效)的数字,用户名可能是用户的网络登录用户名,时间戳是以“yyyymmddHHss” GMT/UTC 时间格式的日期时间,密钥哈希是客户端 ID + 用户名 + 时间戳 + 私钥 的哈希(SHA1)值。私钥在双方都相同,这有一些小妥协——密钥将在双方(数据库或网络上的安全文件位置)以加密形式安全存储。
Web 应用程序接收 POST 值,验证时间戳是否在允许的窗口内(+/- 2 分钟),然后应用程序从数据库中获取私钥副本,使用该密钥创建客户端 ID、用户名、时间戳和私钥的哈希值。此哈希值与 POST 的哈希密钥进行比较。如果两个值匹配,则允许用户通过设置覆盖登录来访问应用程序。
要记住的重要一点是,这仅在通过 SSL 访问时才有效。否则,任何人都可以嗅探密钥哈希并破解会话。但是,通过获取哈希值,他们可能无法获得任何其他用户的数据。SHA1 非常难以解密。此外,在客户端 POST 代码中,创建 POST URL 和重定向不应花费太多时间——否则,它允许用户使用“查看源”来获取哈希值。无论如何,这可能没有用,但这是找到哈希值的一种可能性。
源代码
大部分代码都是自解释的,我已经给出了每个项目代码的用途。
- WebConfig
<appSettings> <add key="PassThroughAccountId" value="1"/> <add key="URLForward" value="https:///passthroughapp/PassThrough.aspx"/> <add key="Key" value="WAga1weKy7Tcbonly1Af76jbj7jbb"/> </appSettings>
- 创建通行证值。
在 TestPassThrough 项目中,PassthroughTest.aspx.cs 包含根据文本框中输入的凭据生成哈希值的代码。基本上,我只是使用这种方法来测试应用程序。在生产应用程序中,您可能需要从网络登录凭据中获取用户详细信息。
private void Page_Load(object sender, System.EventArgs e) { KeyToHash = ConfigurationSettings.AppSettings.Get("Key"); TextBoxKey.Text=KeyToHash.ToString(); if (!Page.IsPostBack) { txtTimeline.Text = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); Submit.Disabled = true; } } private void Submit_ServerClick(object sender, System.EventArgs e) { string toSend; toSend = "PostToAppl.aspx" + "?var1=" + txtUsername.Text + "&var2=" + txtTimeline.Text + "&var3=" + TextBoxKey.Text + "&var4=" + txtClientId.Text; Submit.Disabled = true; HttpContext.Current.Response.Redirect(toSend); } private void GenHash_Click(object sender, System.EventArgs e) { string Username = txtUsername.Text; string Timeline = txtTimeline.Text; // KeyToHash = "326FD166-85B8-4990-93DB-A8802C1C74CA"; string SentHash = Username + Timeline + txtClientId.Text + KeyToHash; string base64HashValue = FormsAuthentication.HashPasswordForStoringInConfigFile(SentHash, "SHA1"); TextBoxKey.Text = base64HashValue; Submit.Disabled = false; } private void GetUTCTime_Click(object sender, System.EventArgs e) { txtTimeline.Text = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); }
- POST 到应用程序
我将值通过查询字符串发送到另一个 aspx 页面(该页面会自动将值 POST 到应用程序)。我这样做是为了验证正在发送的值。您可以避免这样做,只需一步完成。
private void Page_Load(object sender, System.EventArgs e) { Username_Form = Request.QueryString.Get(0); Timeline_Form = Request.QueryString.Get(1); Hash_Form = Request.QueryString.Get(2); ClientId = Request.QueryString.Get(3); URLForward = ConfigurationSettings.AppSettings.Get("URLForward"); }
//Code Behind : <%@ Page language="c#" Codebehind="PostToAppl.aspx.cs" AutoEventWireup="false" Inherits="Passthrough.UI.Web.PostToAppl" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <title>TestPostForm</title> <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING="GridLayout" onload="Redirect();"> <form action='<%=URLForward%>' method='post' Target='_blank' name='frmLogon'> <table border='1'> <tr> <td class='bold'>UserName</td> <td> </td> <td><input type='text' name='Username' value='<%=Username_Form%>' size='30' MAXLENGTH='30' readOnly> </td> </tr> <tr> <td class='bold'>Timeline</td> <td> </td> <td><input type='text' name='Request Time' value='<%=Timeline_Form%>' size='14' MAXLENGTH='14' readOnly></td> </tr> <tr> <td class='bold'>Hash</td> <td> </td> <td><input type='text' name='Hashed Value' value='<%=Hash_Form%>' size='50' MAXLENGTH='50' readOnly></td> </tr> <tr> <td class='bold'>ClientId</td> <td> </td> <td><input type='text' name='Hashed Value' value='<%=CleintId%>' size='50' MAXLENGTH='50' readOnly></td> </tr> <tr> <td colspan='3'></td> </tr> </table> <script language="JavaScript"> function Redirect() { document.frmLogon.submit(); } </script> </form> </body> </HTML>
- Application
应用程序代码接收 POST 的值,并从数据库或 Web.config 获取私钥(目前,我已将密钥硬编码在代码本身中),生成 HASH 值,并将其与提供的 HASH 值进行比较。如果匹配,则在 Session 中设置 User 对象并允许应用程序继续。
编码愉快。
结论
欢迎任何建议,这是经过编辑的应用程序的一部分。它可能包含一些不完整或注释掉的代码,因为我的目的是解释并提供通行证身份验证的大纲。可能还有其他更好的方法。因此,请谨慎使用,并探索所有可能性以防止会话被黑客攻击。