WinForms - 使用自定义主体与 AspNetDb
描述如何在 WinForms 应用程序中使用带有 AspNetDb 安全数据库的自定义 principal 实现。
引言
我们的许多 ASP.NET 应用程序使用带有 SqlRoleProvider 的 AspNetDb 进行安全和组管理。 .NET 附带的 Membership API 在避免这种标准身份验证行为的代码重复方面非常出色。
您的 WinDorms 应用程序可以使用与 ASP.NET 应用程序相同的 API,但它必须引用 System.Web 库。
但是,对于许多应用程序来说,并不需要这个完整的 API。 我们只需要找出用户是哪些组的成员,并根据成员资格授予他们对资源的访问权限。 本文将演示一种使用 AspNetDb 进行用户角色管理但绕过 Membership API 的技术。
背景场景
您可能会遇到这样一种情况:您想使用标准的 Windows 身份名称,但不希望使用 Active Directory 进行组管理。 您希望自定义角色成员资格的控制权。 这可能是因为您想将所有应用程序安全保存在一个地方(即 AspNetDb),或者您只是想将应用程序特定的安全保存在域之外。 域管理员可能不想负责创建和分配应用程序的角色成员资格。
特别是对于内部公司应用程序,用户希望采用“单点登录”方法来访问应用程序。 他们使用他们的 Windows 凭据登录到他们的终端,这应该允许他们访问他们需要的所有应用程序;为不同的应用程序单独登录会让人感到厌倦。 因此,我们已经有了身份验证过程的“用户名”部分。 这是他们在 Windows 凭据中指定的名称。 现在,我们只需要提供测试组成员资格的功能。
数据库
如果您还没有 AspNetDb 的实例,以下文章定义了如何创建数据库。
注意:您需要选择一个 SQL Server 实例,而不是您的本地计算机。
数据库管理
我们使用 IDesign Credentials Manager,它比基于 IDE 的“Web Application Tool”更可取,因为它允许任何人管理凭据,而不仅仅是那些安装了 VS2005 IDE 的人。
我们使用此应用程序的自定义版本,它允许我们管理远程 SQL 数据库(源代码将在获得许可后提供)。
创建并分配用户
Credentials Manager 提供了一个用于管理 ASP.NET 安全模型的界面。 创建您的应用程序名称,将用户和角色分配给应用程序,然后将用户添加到角色中。
Using the Code
您的应用程序现在应该检查应用程序以查看用户是哪些角色的成员。 我们可以使用 IPrincipal
和 IIdentity
的自定义实现来做到这一点。 这些实现基于构成 CSLA.Net framework 的代码,但它们已进行自定义以使用从 AspNetDb 提供角色管理的过程。
IIdentity 的实现
namespace System.Security
{
using System;
using System.Collections;
using System.Configuration;
using System.Security.Principal;
using System.Data;
using System.Data.SqlClient;
/// <summary>
/// Implements a custom Identity class for use with the AspNetDb
/// </summary>
[Serializable()]
public class AspNetDbIdentity : IIdentity
{
string usernameField = string.Empty;
ArrayList rolesList = new ArrayList();
#region IIdentity
/// <summary>
/// Implements the IsAuthenticated property
/// defined by IIdentity. Returns True
/// if the user is a member of at least 1 role
/// </summary>
bool IIdentity.IsAuthenticated
{
get
{
return (rolesList.Count > 0);
}
}
/// <summary>
/// Implements the AuthenticationType property defined by IIdentity.
/// </summary>
string IIdentity.AuthenticationType
{
get
{
return "AspNetDb";
}
}
/// <summary>
/// Implements the Name property defined by IIdentity.
/// </summary>
string IIdentity.Name
{
get
{
return usernameField;
}
}
#endregion
internal bool IsInRole(string role)
{
return rolesList.Contains(role);
}
#region Ctor
/// <summary>
/// Ctor
/// </summary>
/// <param name="userName">The name of the user defined in the AspNetDb
public AspNetDbIdentity(string userName)
{
this.usernameField = userName;
this.rolesList.Clear();
using (SqlConnection cn = new SqlConnection(
ConfigurationManager.ConnectionStrings[
"SecurityConnectionString"].ConnectionString))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "aspnet_UsersInRoles_GetRolesForUser";
cm.CommandType = CommandType.StoredProcedure;
cm.Parameters.AddWithValue("@ApplicationName",
ConfigurationManager.AppSettings["ApplicationName"]);
cm.Parameters.AddWithValue("@UserName", userName);
using (SqlDataReader dr = cm.ExecuteReader())
{
while (dr.Read())
{
rolesList.Add(dr.GetString(0));
}
}
}
}
}
#endregion
}
}
正如您将在构造中看到的那样,您需要在应用程序的App.config文件中定义两个应用程序设置才能使应用程序正常工作。 确保您先设置这两个设置。
- 为您的 SQL 实例定义一个连接字符串,该实例托管 AspNetDb
<connectionstrings>
<add name="SecurityConnectionString"
connectionstring="Data Source=YOURSERVER;
Initial Catalog=AspNetDb;User ID=MyUser;Password=Password"
providername="System.Data.SqlClient">
</add>
</connectionstrings>
<appsettings>
<add key="ApplicationName" value="Harmony">
</add>
</appsettings>
IPrincipal 的实现
namespace System.Security
{
using System;
using System.Security.Principal;
using System.Threading;
/// <summary>
/// Implements a custom Principal class that used the AspNetDb database
/// to check for role membership
/// </summary>
[Serializable()]
public class AspNetDbPrincipal : IPrincipal
{
AspNetDbIdentity identityField;
#region IPrincipal
/// <summary>
/// Implements the Identity property defined by IPrincipal.
/// </summary>
IIdentity IPrincipal.Identity
{
get
{
return identityField;
}
}
/// <summary>
/// Implements the IsInRole property defined by IPrincipal.
/// </summary>
bool IPrincipal.IsInRole(string role)
{
return identityField.IsInRole(role);
}
#endregion
#region Ctor
/// <summary>
/// Default construct
/// </summary>
/// <param name="identity">The AspNetDbIdentity assigned to the user
public AspNetDbPrincipal(AspNetDbIdentity identity)
{
AppDomain currentdomain = Thread.GetDomain();
currentdomain.SetPrincipalPolicy(PrincipalPolicy.UnauthenticatedPrincipal);
IPrincipal oldPrincipal = Thread.CurrentPrincipal;
Thread.CurrentPrincipal = this;
try
{
if (!(oldPrincipal.GetType() == typeof(AspNetDbPrincipal)))
currentdomain.SetThreadPrincipal(this);
}
catch
{
// failed, but we don't care because there's nothing
// we can do in this case
}
identityField = identity;
}
#endregion
}
}
使用对象
构造对象遵循与 WindowsPrincipal
和 WindowsIdentity
对象相同的模式。 将 Environment.Username
传递给 AspNetDbIdentity
,然后使用 AspNetDbIdentity
实例来创建 AspNetDbPrincipal
。
AspNetDbIdentity identity = new AspNetDbIdentity(Environment.UserName)
AspNetDbPrincipal principal = new AspNetDbPrincipal(identity);
应用程序安全检查
构造 AspNetDbPrincipal
对象会将主线程设置为 AspNetDbPrincipal
实例,因此您可以通过访问 Thread.CurrentPrincipal
对象来使用编程安全或声明式安全。
// This checks that the AspNetDbPrincipal is a member of the administrator role
Thread.CurrentPrincipal.IsInRole("Administrator");
测试应用程序
本文附带的测试应用程序演示了使用来自 AspNetDb 的数据在 WinForms 应用程序中实现基于角色的安全所需的所有设置。
历史
- 2010 年 6 月 - 初始版本。