单一登录 Web 应用程序的企业级安全系统

单一登录 Web 应用程序的企业级安全系统

发布日期: 8/17/2004 | 更新日期: 8/17/2004

Paul D. Sheriff
PDSA, Inc.

适用范围:
Microsoft® ASP.NET

摘要:揭示能够以单一登录的方式登录多个 Web 应用程序的技术。本文还提供了示例代码,使您能够在完全使用单一登录的情况下创建强大的企业安全系统方面有一个良好的开端。

返回页首singlesignon_01

1:涵盖内部用户和外部用户的单一登录解决方案的示例

Intranet 解决方案

1 的左上角可以看到内部用户使用浏览器浏览到特定的网站(第 1 步)。这个网站验证(第 2 步)用户的 Windows 凭据(通过 Active Directory)。如果用户是有效的 Windows 用户,则允许其进入该站点。通过身份验证后,将检索用户的标识,并调用包含指定用户能够运行的应用程序列表的数据库表(第 3 步)。这些应用程序将显示在 DataGrid 中,以供用户选择。

用户单击希望运行的应用程序后,将生成一个唯一的、只能使用一次的令牌(第 4 步)。此令牌和用户标识将被存储在另一个数据库表中。然后此令牌被传递给用户要运行的 Web 应用程序中的一个特定页面(通过查询行)。此特殊页面从查询行读取该令牌,然后验证在数据库表中是否存在此令牌(第 5 步)。如果令牌存在,它将在数据库中检索登录 ID,然后删除存储此令牌的记录。此操作能够防止其他人再次使用此令牌并将登录 ID 发送回 Web 应用程序。

在了解了 Web 应用程序中的用户标识后,您需要生成一个 ASP.NET 窗体身份验证票据,因为在 Intranet 中,所有的 Web 应用程序都将使用基于窗体的身份验证。此票据将被正在浏览站点中所有的安全页面的用户所使用。

Extranet 解决方案

想要访问 Web 应用程序的外部用户(您所在域之外的用户)将被定向到与内部用户不同的起始页面(参见 2)。内部用户和外部用户被定向到的 Web 应用程序采取了基于窗体的身份验证的安全措施。当外部用户试图打开此 Web 应用程序中的任何页面时,他们将被自动重定向至登录页面。此登录页面与对内部用户进行身份验证的页面不同。用户必须输入其凭据,然后系统将调用同一个数据库,以确定该用户对此应用程序是否有效。如果有效,则将为此用户会话生成正常的基于窗体的身份验证 cookie。

返回页首singlesignon_02

2:设计和开发单一登录解决方案只需要几个类和页面

返回页首singlesignon_04

4:实现单一登录系统的完整角色所需的若干表

返回页首singlesignon_09

9AppLauncherxx 解决方案包含 AppLauncherxx 项目和对 AppLauncherDataxx 项目的引用

  返回页首 singlesignon_10

10:每个 Web 应用程序都将使用结合了 AppLoginLogin Default 页面的 AppLauncherDataxx 项目

返回页首返回页首返回页首返回页首 返回页首

Apps 类

此类用于检索指定用户的应用程序列表。它还将生成一个新令牌、根据给定的令牌检索用户的标识信息以及删除令牌。

表 1:Apps 类的方法
方法名称说明

GetAppsByLoginID

根据给定用户的域登录 ID,查找与此用户关联的应用程序,并返回应用程序的 DataSet。

CreateLoginToken

创建并返回新登录令牌。

GenerateToken

生成新令牌的方法。在本版本中,将使用一个简单的 GUID 作为令牌。

VerifyLoginToken

根据给定令牌,此方法将验证数据库中是否存在此令牌。它将创建 AppToken 类的一个实例,在写入相应的信息后将其返回。

DeleteToken

删除表中的令牌。

AppToken 类

此类包含从 Apps 类的 VerifyLoginToken 方法返回令牌信息时所需的四个属性。表 2 说明了此类包含的四个属性。

表 2:AppToken 类的属性
属性说明

LoginID

字符串,其值表示用户的登录 ID。

AppName

字符串,其值表示与此 AppToken 记录有关的应用程序名称。

LoginKey

整数类型,其值表示 esUsers 表中用户的主键。

AppKey

整数类型,其值表示 esApps 表中应用程序的主键。

AppUserRoles 类

此类用于检索试图登录应用程序的用户的信息。其中的一个方法根据给定的登录 ID 和应用程序键值检查登录是否有效。另一个方法返回给定用户的角色集。还有一个方法根据登录 ID 和应用程序键值返回 esUser 表的主键。

AppLauncher 应用程序中的 Default.aspx

此 Web 页类将检索通过了 IIS 身份验证的 Windows 用户,并返回允许这些用户访问的应用程序的列表。它将在 DataGrid 中显示此列表(图 3),并允许用户单击其中特定的应用程序。用户单击应用程序后,此页将生成一个新令牌,并将此令牌和用户 ID 存储到 esAppToken 表,然后调用此应用程序将令牌传递给该 Web 应用程序中的 AppLogin.aspx 页。

singlesignon_03

3:应用程序启动器显示允许登录的用户运行的应用程序的列表

每个网站中的 AppLogin.aspx

只能从应用程序启动器调用此 Web 页类。若有任何其他的应用程序试图调用此页,它会将用户重定向至网站的 Default.aspx 页。因为每个 Web 应用程序都使用基于窗体的身份验证,所以此操作将强制 ASP.NET 将用户重定向至站点中的 Login.aspx 页。

如果使用令牌从应用程序启动器站点调用此页,那么此页将调用 Apps 类中的方法,以验证此令牌是否有效。如果令牌有效,则 Apps 类将返回一个 AppToken 对象,以便此页能够使用此对象中的信息创建基于窗体的身份验证的用户。

每个网站中的 Login.aspx

这是常规的 Web 登录页面,它要求用户输入凭据,并在数据库中检查这些凭据,以确保是有效用户;此外,如果是有效用户,则还将创建身份验证票据,并在用户进入站点时,重定向至用户请求的页面。

每个网站中的 Default.aspx

这是每个网站的主登陆页面。只有通过了 AppLogin 或 Login Web 页面的身份验证的用户才能够浏览此页以及站点中所有其他页。

esApps

此表包含企业中所有 Web 应用程序的列表。除应用程序的名称以外,表中还包含应用程序的详细说明信息以及应用程序的 URL。URL 是完整形式的 URL,并且应以 AppLogin.aspx 页结尾。应用程序启动器中的 default.aspx 页将负责在重定向至 Web 应用程序前向 URL 中添加字符串 “Token=<GeneratedToken>”。

singlesignon_05thumb

5esApps 表的示例数据

esUsers

此表列出可以使用应用程序的所有用户。您需要为所有内部用户复制您的用户域登录 ID。您可能还希望为外部用户添加密码字段。

singlesignon_06

6esUsers 表的示例数据

esAppsUsers

此表将 esUsers 表中的用户与 esApps 表中他们能够运行的应用程序关联起来。表中的数据只有 esUsers 表和 esApps 表的外键。

esAppRoles

此表包含每个应用程序的角色集。例如,一个应用程序可能包含“Admin”和“User”角色,而其他应用程序则可能包含“User”和“Supervisor”规则。

singlesignon_07thumb

7esAppRoles 表的示例数据

esAppUsersRoles

此表包含应用程序中每个用户的每一个角色的列表。在应用程序“HR”中,用户“Joe”可能是“Supervisor”,但在应用程序“Payroll”中,则可能是“Admin”和“User”。

esAppToken

esAppToken 包含由登录门户应用程序生成并传递给单个 Web 应用程序的令牌。这些记录在通常情况下应只存在两秒(或更短的时间),因为正被调用的 Web 应用程序在从此表中收集信息之后,会立即删除此令牌。这样可以防止他人再次使用此令牌。

singlesignon_08thumb

8 esAppToken 表的示例数据

AppLauncherxx 中的 Default.aspx

应用程序启动器网站只需要一个 Web 页:即 default.aspx。此 Web 页从页面的 User 对象中读取用户的 Windows 登录 ID,并加载数据库中为此用户定义的应用程序列表。以下是加载 default.aspx 页面时调用的 Page_Load 事件过程的代码。

// C#private void Page_Load(object sender, System.EventArgs e){  // 显示用户名称  // 不带域前缀  lblLogin.Text = "Applications Available for: " +     Apps.LoginIDNoDomain(User.Identity.Name);  AppLoad();}' VB.NETPrivate Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load  ' 显示用户名称  ' 不带域前缀  lblLogin.Text = "Applications Available for: " & _    Apps.LoginIDNoDomain(User.Identity.Name)  AppLoad()End Sub

调用 Apps 类的静态方法 LoginIDNoDomain 是为了去除域前缀。如果登录 ID 为“Ken”的用户通过了名为“PDSA”的域的身份验证,那么 User.Identity.Name 属性将返回“PDSA/Ken”。而此方法则仅返回字符串“Ken”。

加载此用户能够运行的应用程序

AppLoad 方法使用 Apps 类的一个实例检索允许此用户运行的应用程序的 DataSet。本文后面的内容将显示 Apps 类中的 GetAppsByLoginID 方法。

// C#private void AppLoad(){  Apps app = new Apps();  try  {    // 为此用户加载应用程序    grdApps.DataSource =      app.GetAppsByLoginID(User.Identity.Name);    grdApps.DataBind();  }  catch (Exception ex)  {    lblMessage.Text = ex.Message;  }}' VB.NETPrivate Sub AppLoad()  Dim app As New Apps  Try    ' 为此用户加载应用程序    grdApps.DataSource = app.GetAppsByLoginID(User.Identity.Name)    grdApps.DataBind()  Catch ex As Exception    lblMessage.Text = ex.Message  End TryEnd Sub

LinkButton 控件

default.aspx 页面的 DataGrid 控件显示指定用户的应用程序列表后(参见 3),用户就可以选择其中的应用程序。DataGrid 中用于显示超级链接以供用户单击的控件即为 LinkButton。在 Web 页面中,LinkButton 定义如下:

<asp:LinkButton id=lnkApp runat="server" AppID='<%# DataBinder.Eval(Container.DataItem, "iAppID") %>' UserID='<%# DataBinder.Eval(Container.DataItem, "iUserID") %>' CommandArgument='<%# DataBinder.Eval(Container.DataItem, "sURL") %>' Text='<%# DataBinder.Eval(Container.DataItem, "sAppName") %>'></asp:LinkButton>

从上述代码可以看出,增添了一些 esApps 表的主键 (iAppID) 和 esUsers 表的主键 (iUserID) 的附加属性。这些属性以及 CommandArgument 中的 URL 提供了可存储到数据库中的足够的用户信息。稍后您会看到,我们将在 ItemCommand 事件过程中检索这些附加属性。

提示: 可以向服务器控件添加任何想要的属性。ASP.NET 将忽略这些属性,但是您可以使用服务器控件的 Attributes 属性检索它们的值。

ItemCommand 事件

用户单击 DataGrid 中的 LinkButton 控件后,将调用 ItemCommand 事件过程。在此方法中,需要创建 Apps 类的一个新实例,并检索 LinkButton 控件,以便获取属性,然后调用 Apps 类中的 CreateLoginToken 方法,以将此数据存储到数据库中的 esAppToken 表中。最后,从此方法检索出令牌后,此令牌将与 LinkButton 的 CommandArgument 属性中的 URL 连接,然后调用 Response.Redirect,以调用令牌所传递的 Web 应用程序。

// C#private void grdApps_ItemCommand(object source,   System.Web.UI.WebControls.DataGridCommandEventArgs e){  Apps app = new Apps();  bool redirect = false;  string token = String.Empty;  LinkButton lb;  try  {    lb = (LinkButton) e.Item.Cells[0].Controls[1];        // 为此用户或应用程序创建令牌    token = app.CreateLoginToken(      lb.Text,       User.Identity.Name,       Convert.ToInt32(lb.Attributes["UserID"]),       Convert.ToInt32(lb.Attributes["AppID"]));    redirect = true;  }  catch (Exception ex)  {    redirect = false;    lblMessage.Text = ex.Message;  }  if (redirect)  {    // 重定向至生成的令牌中    // 传递的 Web 应用程序    Response.Redirect(e.CommandArgument.ToString() +      "?Token=" + token, false);  }}' VB.NETPrivate Sub grdApps_ItemCommand(ByVal source As Object, _  ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _  Handles grdApps.ItemCommand  Dim app As New Apps  Dim boolRedirect As Boolean  Dim token As String  Dim lb As LinkButton  Try    lb = DirectCast(e.Item.Cells(0).Controls(1), LinkButton)    ' 为此用户或应用程序创建令牌    token = app.CreateLoginToken(lb.Text, _      User.Identity.Name, _      Convert.ToInt32(lb.Attributes("UserID")), _      Convert.ToInt32(lb.Attributes("AppID")))    boolRedirect = True  Catch ex As Exception    boolRedirect = False    lblMessage.Text = ex.Message  End Try  If boolRedirect Then    ' 重定向至生成的令牌中     ' 传递的 Web 应用程序    Response.Redirect(e.CommandArgument.ToString() & _      "?Token=" & token, False)  End IfEnd Sub

您会注意到,在上面的代码中,检索了 e.Item 参数中的 LinkButton 控件。e.Item 用于引用您在 DataGrid 中单击的行。您可以在 LinkButton 所在的列后检索所单击的 LinkButton 控件的特定实例。在本例中,它在 Cells(0) 中。在此单元格中,可以在 Controls(1) 后取得该控件。该控件位于位置一 (1) 的原因是 DataGrid 中所使用的、允许我们将 LinkButton 置于单元格中的 ItemTemplate 被看作是元素零 (0)。

检索出 LinkButton 后,就可以使用 Attributes 属性检索在设置 LinkButton 时存储的 UserID 和 AppID。Attributes 属性是您向服务器控件添加的所有附加属性的集合,这些附加属性不是初始控件定义的组成部分。

修改 Web.Config

在 Web 应用程序对用户进行身份验证之前,必须将 Web.Config 文件中的 <authentication> 元素设置为“Windows”。此外,还必须拒绝 Web.config 中的 <authorization> 元素中的匿名用户。

<authorization>  <deny users="?" /></authorization> 

设置这两个元素可从浏览器强制服务器检索用户的 Windows 凭据。当然,只有当您在用户所登录的域中使用 Internet Explorer 时,它才会起作用。

最后一项要完成的工作是存储连接字符串,以便从数据库的表中获取。在本文的示例中,我只使用了 Web.config 中的 <appSettings> 部分存储连接字符串。

<appSettings><add key="eSecurityConnectString"    value="server=(local);Database=eSecurity;uid=myUserID;pwd=myPassword" /></appSettings>

在本文中,我在 SQL Server™ 中创建了名为 eSecurity 的数据库,并创建了本文前面内容中所述的所有的表。本文的示例代码包含一个 SQL 脚本,可以运行此脚本以在 SQL Server 数据库中创建表。如果使用其他数据库系统,则需要根据您的数据库对脚本进行适当的修改。

修改 Web.Config

要创建集成了单一登录系统的 Web 应用程序,首先需要修改 Web.config 文件,并创建 <appSettings> 区段,以存储连接字符串,从而与 eSecurity 数据库进行交互。还需要存储应用程序 ID 和应用程序名称,以供外部用户进入时使用。因为外部用户进入时,尚未创建令牌,这时需要知道用户选择的应用程序,以便在建立用户角色时加载要使用的 AppToken 对象。在本文后面的内容中,您会了解如何这样做。

<appSettings>  <add key="eSecurityConnectString"    value="server=(local);Database=           eSecurity;uid=mUserID;pwd=myPassword"></add>  <add key="eSecurityAppID" value="1"></add>  <add key="eSecurityAppName" value="Payroll"></add></appSettings>

您还需要设置 Web 应用程序,以使用基于窗体的身份验证。要进行此操作,请修改 <authentication> 元素,如下所示。

<authentication mode="Forms">    <forms name="AppTest" loginUrl="Login.aspx" /></authentication>

最后,为了拒绝匿名用户访问,还需要修改 <authorization> 元素。

<authorization>  <deny users="?" /></authorization> 

AppLogin.aspx 页

请记住,从应用程序启动器调用的每个应用程序都会调用 AppLogin 页,并向此页传递生成的令牌。AppLogin 页会验证此令牌是否正确,从 esAppToken 表检索相关的信息,然后删除 esAppToken 中的记录,以使该令牌不被重复使用。

// C#private void Page_Load(object sender, System.EventArgs e){   VerifyToken();}private void VerifyToken(){  Apps app = new Apps();  AppToken al;  try  {    al = app.VerifyLoginToken(      Request.QueryString["Token"].ToString());    if(al.LoginID.Trim() == "")    {      // 非有效登录      // 将其重定向至默认页面       // 这将使用户转至登录页面      Response.Redirect("default.aspx");    }    else    {      // 创建窗体身份验证 Cookie      // 设置窗体身份验证变量      FormsAuthentication.Initialize();      FormsAuthentication.SetAuthCookie(        al.LoginID.ToString(), false);      // 设置应用程序令牌对象      Application["AppToken"] = al;      // 重定向至默认页面      Response.Redirect("default.aspx");    }  }  catch  {    // 通过默认页面将用户重定向至登录页面    Response.Redirect("default.aspx");  }}' VB.NETPrivate Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load  VerifyToken()End SubPrivate Sub VerifyToken()  Dim app As New Apps  Dim al As AppToken  Try    al = app.VerifyLoginToken( _      Request.QueryString("Token").ToString())    If al.LoginID.Trim() = "" Then      ' 非有效登录      ' 将其重定向至默认页面       ' 这将使用户转至登录页面      Response.Redirect("default.aspx")    Else      ' 创建窗体身份验证 Cookie      ' 设置窗体身份验证变量      FormsAuthentication.Initialize()      FormsAuthentication.SetAuthCookie( _        al.LoginID.ToString(), False)      ' 设置应用程序令牌对象      Application("AppToken") = al      ' 重定向至默认页面      Response.Redirect("default.aspx")    End If  Catch    ' 通过默认页面将用户重定向至登录页面    Response.Redirect("default.aspx")  End TryEnd Sub

在 VerifyLogin 方法中,首先检查令牌是否有效。这一操作通过调用 Apps 类中的 VerifyLoginToken 方法完成。此方法返回 AppToken 类的一个实例。如果此类的 LoginID 属性存在值,就表明此用户是有效用户。如果没有值,则令牌无效。在令牌无效的情况下,此方法将用户重定向到网站中的 default.aspx 页面。当然,如果打开了基于窗体的身份验证,用户会被强制重定向至 Login.aspx 页面,被请求重新登录。

调用 FormsAuthentication.Initialize 和 FormsAuthentication.SetAuthCookie 方法可以向外发送窗体验证 cookie。这会将内存中的 cookie 发送到浏览器。每次用户返回此站点时,ASP.NET 运行库都会检查此 cookie。

VerifyLoginToken 方法

此方法通过查询行接受生成的令牌,并对其进行检查,以确保令牌有效。此方法会转到 esAppToken 表检查此令牌。如果在表中找到此令牌,则将表中所有的值置于 AppToken 对象的各个属性中,然后从此方法返回该 AppToken 对象。

// C#public AppToken VerifyLoginToken(string Token){  AppToken al = new AppToken();  DataSet ds = new DataSet();  SqlCommand cmd;  DataRow dr;  SqlDataAdapter da;  string sql;  sql = "SELECT iAppTokenID, sAppName, sLoginID, ";  sql += " iAppID, iUserID ";  sql += " FROM esAppToken";  sql += " WHERE sToken = @sToken ";  try  {    cmd = new SqlCommand(sql);    cmd.Parameters.Add(new       SqlParameter("@sToken", SqlDbType.Char));    cmd.Parameters["@sToken"].Value = Token;    cmd.Connection = new SqlConnection(mConnectString);    da = new SqlDataAdapter(cmd);    da.Fill(ds);    if (ds.Tables[0].Rows.Count > 0)    {      dr = ds.Tables[0].Rows[0];      al.LoginID = dr["sLoginID"].ToString();      al.AppName = dr["sAppName"].ToString();      al.AppKey = Convert.ToInt32(dr["iAppID"]);      al.LoginKey = Convert.ToInt32(dr["iUserID"]);      DeleteToken(Convert.ToInt32(dr["iAppTokenID"]));    }  }  catch (Exception ex)  {    throw ex;  }  return al;}' VB.NETPublic Function VerifyLoginToken(ByVal Token As String) As AppToken  Dim al As New AppToken  Dim ds As New DataSet  Dim cmd As SqlCommand  Dim dr As DataRow  Dim da As SqlDataAdapter  Dim sql As String  sql = "SELECT iAppTokenID, sAppName, sLoginID, "  sql &= " iAppID, iUserID "  sql &= " FROM esAppToken"  sql &= " WHERE sToken = @sToken "  Try    cmd = New SqlCommand(sql)    cmd.Parameters.Add(New _      SqlParameter("@sToken", SqlDbType.Char))    cmd.Parameters("@sToken").Value = Token    cmd.Connection = New SqlConnection(mConnectString)    da = New SqlDataAdapter(cmd)    da.Fill(ds)    If ds.Tables(0).Rows.Count > 0 Then      dr = ds.Tables(0).Rows(0)      al.LoginID = dr("sLoginID").ToString()      al.AppName = dr("sAppName").ToString()      al.AppKey = Convert.ToInt32(dr("iAppID"))      al.LoginKey = Convert.ToInt32(dr("iUserID"))      DeleteToken(Convert.ToInt32(dr("iAppTokenID")))    End If  Catch ex As Exception    Throw ex  End Try  Return alEnd Function

基于角色的安全性

用户通过 Web 应用程序的验证后,将被重定向至 default.aspx 页面。因为已经向浏览器发送了身份验证 cookie,每次都会将此 cookie 发送回来。在向用户传送请求的页面之前,将调用 global.asax 文件中的 Application_AuthenticateRequest 方法。只有在这个方法中才能够建立要与此用户关联的任何角色。我不打算在这里显示此方法的用法,因为专门有文章对其进行讨论。您可以查看示例代码中使用此方法的例子。基于角色的安全性的代码十分简单。您完全可以改进此代码,使其能够使用缓存。它的目的只是告诉您如何着手去做。

参考资料

ASP.NET Developer's Jumpstart(英文)

Building Secure Microsoft ASP.NET Applications(英文)

关于作者

Paul D. Sheriff 是 PDSA, Inc. (http://www.pdsa.com/products) 的总裁,该公司是 Microsoft 的咨询公司和合作伙伴,提供有关 .NET 的咨询、产品和服务,包括 SDLC 文档和体系结构框架。Paul 是 Microsoft 在南加利福尼亚的地区主管。他撰写的 .NET 方面的书籍包括《ASP.NET Developer's Jumpstart》(Addison-Wesley) 以及在 PDSA 网站上列出的一些电子图书。Paul 的联系邮件是 PSheriff@pdsa.com

© 2004 Microsoft Corporation 版权所有。保留所有权利。使用规定。

单一登录 Web 应用程序的企业级安全系统                   单一登录 Web 应用程序的企业级安全系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值