关于 .NET 中使用 Principal 和 Identity 对象实现安全模型的深入探讨






4.81/5 (29投票s)
2003年10月10日
8分钟阅读

188598

1154
本文介绍了 .NET 中 Principal 和 Identity 对象背后的概念,并提出了一种在 .NET 应用程序中采用这些对象的安全模型。
引言
在设计应用程序时,“安全”这个词总是萦绕在我脑海中。在我看来,任何应用程序的安全都应被置于首位,因为黑客们无时无刻不在活动。安全应该是应用程序本身所具备的,而不是一项附加功能。
Microsoft .NET 作为一项技术,提供了多种实现安全性的模型/模式。这些模式可以根据我们的需求进行定制,因此具有可扩展性。
Microsoft .NET 框架提供了名为“Principal”和“Identity”的对象,这将是我们思考的主要方向。本文主要关注通过采用“Principal”和“Identity”对象来实现安全模型,并为 .NET 应用程序提出了一个安全模型。
什么是 Principal 和 Identity 对象?
在我们深入探讨之前,理解 Principal 和 Identity 对象的根本目的是对最终用户进行身份验证和授权是非常重要的。
简单来说,身份验证是验证用户身份的过程,而授权是根据用户身份授予对应用程序/功能的访问权限的过程。
身份
此对象存储有关用户的信息。它封装了正在进行身份验证的用户的名称。我们可以将此对象与一个存储用户信息的(用户名)数据存储关联起来。Identity 对象有两种类型。
WindowsIdentity
:此对象封装了 Windows 登录用户名以及 Windows 用于身份验证的协议类型。任何时候需要了解 Windows 登录用户名时,WindowsIdentity
对象都包含这些信息。GenericIdentity
:也存储用户信息,但当应用程序需要实现自定义登录时使用。可以通过指定用户名(通常通过自定义登录屏幕获取)来创建此对象,并将其传播到各个应用程序层以用于授权。
我们将在本文稍后详细讨论这一点。
Principal
此对象代表正在运行的进程或 AppDomain
的安全上下文。每个线程都有一个上下文,它持有 Principal 对象。Principal 对象封装了用户的身份以及角色/组成员身份。基于角色的安全最好通过 Principal 对象来实现。可以通过用户的身份和角色来创建 Principal 对象。在实现应用程序的基于角色的授权时,Principal 对象非常有用。
Principal 对象有两种类型:
GenericPrincipal
:此对象封装了身份对象和角色。在实现具有基于角色授权的自定义登录时,需要创建GenericPrincipal
对象。WindowsPrincipal
:也存储用户的身份和 Windows 组成员身份。这非常适合为应用程序实现基于角色的安全。
使用 Principal 对象实现基于角色的安全
通过考虑一些实际场景,可以更好地理解上述理论。
场景 A:Windows 身份验证(Identity)与数据库提供的角色
假设
在这种情况下,如果数据库中存在用户的角色,则该用户是有效用户。如果数据库中不存在用户的角色,则该用户是匿名用户,因此不应获得系统任何功能的访问权限。此外,只有在分配了角色时,用户才会在数据库中有条目。
在大多数情况下,对于内网/Windows 应用程序,首选 Windows 身份验证,而角色是应用程序特定的,并且存在于数据存储中。在这种情况下,我们将创建一个 GenericPrincipal
对象,其身份为 Windows 登录用户,角色从数据存储中为特定身份提取。这相当于使用 Windows 身份进行身份验证,并使用从数据库构建的 GenericPrincipal
对象进行授权。
也就是说,我们将检索 Windows 身份,从身份对象中获取用户名,并查询数据存储以获取用户的角色。一旦获得用户角色,我们就可以构造一个 GenericPrincipal
对象,并将其设置为上下文/线程。
现在,显而易见的问题是,谁会访问上下文,授权如何发生?让我们考虑一个假设情况,我们需要根据用户角色授予对应用程序功能的访问权限。
身份验证
从用户的角度来看,这将是一个无缝的登录过程。可以通过以下代码检索用户身份:
Windows 应用程序
WindowsIdentity.GetCurrent()
方法将提供登录用户的身份对象。
Imports System.Security.Principal
Module SecurityModule
Sub Main()
Dim winIdent As WindowsIdentity = WindowsIdentity.GetCurrent()
Console.WriteLine(winIdent.Name())
End Sub
End Module
Web 应用程序
通过执行以下操作,可以获取 Windows 用户的身份:
- 在 _Web.Config_ 文件中将身份验证模式设置为
Windows
,并拒绝匿名用户的授权。 - 在 _Global.asax_ 文件中实现
WindowsAuthentication_OnAuthenticate
事件。此事件传递的WindowsAuthenticationEventArgs
参数提供了 Windows 用户的身份。
<!--Web.Config File Data-->
<authentication mode="Windows"/>
<authorization>
<deny users="?" /> <!--Deny anonymous users -->
<allow users="*" /> <!--Allow all users -->
<authorization/>
_Global.asax_ 代码:以下事件会在 Web 应用程序的每个请求时触发。
Sub WindowsAuthentication_OnAuthenticate(ByVal Source As Object, _
ByVal e As WindowsAuthenticationEventArgs)
Dim userIdentity As String
userIdentity = e.Identity.Name()
End Sub
Authorization
为了授权用户,我们必须将角色与身份相关联,然后构建 Principal 对象。对于从上述 _global.asax_ 事件获得的身份,查询数据库并检索角色。将在 Web 应用程序的每个页面中执行授权检查,以授予或拒绝对特定功能/页面的访问。
以下代码解释了授权用户访问应用程序的完整流程:
Windows 应用程序
在应用程序启动时(如果适用),根据身份从数据库获取相应的角色。使用角色和身份,需要创建 Principal 对象,该对象将在整个应用程序中被访问。以下代码解释了整个过程。
Module SecurityModule
Public genPrincipal As GenericPrincipal
Sub Main()
Dim ident As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim roles() As String
roles = <retrieve from database>
genPrincipal = New GenericPrincipal(ident, roles)
End Sub
End Module
上述 GenericPrincipal 对象将在每个 Windows 窗体中被访问,以决定是否授予或拒绝对所请求功能的访问,如下所示:
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load If genPrincipal.IsInRole("Managers") = False Then MessageBox.Show("You do not have access to this form") End If End Sub
Web 应用程序
对于 Web 应用程序,在进行身份验证检查之前,我们必须构建 Principal 对象。为了让 Web 页面访问此 Principal 对象,必须将其持久化在 HTTP 上下文中。以下代码说明了此过程:
在 WindowsAuthentication_OnAuthenticate
事件中,获取已获得身份的角色。这完成了身份验证部分。紧随其后触发的下一个事件是 Application_AuthenticateRequest
,在此事件中需要构建 Principal 对象。因此,在 WindowsAuthentication_OnAuthenticate
事件中获得的身份和角色必须持久化,以便 Application_AuthenticateRequest
可以构建 Principal 对象。这通过创建包含身份-角色的身份验证票证并将其作为 cookie 发送来实现。现在,Application_AuthenticateRequest
事件可以访问 cookie 并构建 Principal 对象,如下所示:
Sub WindowsAuthentication_OnAuthenticate(ByVal Source As Object, _
ByVal e As WindowsAuthenticationEventArgs)
'Note that since IIS has already performed
'authentication, the provided identity is used.
Dim userIdentity As String
Dim userRoles As String
Dim formsAuthTicket As FormsAuthenticationTicket
Dim httpcook As HttpCookie
Dim encryptedTicket As String
userIdentity = e.Identity.Name()
useRoles =<Retrieve from Database>
formsAuthTicket = New FormsAuthenticationTicket(1, _
e.Identity.Name(), _
DateTime.Now,DateTime.Now.AddMinutes(60), _
False, userRoles)
encryptedTicket = FormsAuthentication.Encrypt(formsAuthTicket)
httpcook = New HttpCookie("authCookie", encryptedTicket)
Response.Cookies.Add(httpcook)
End Sub
Sub Application_AuthenticateRequest(ByVal sender As Object, _
ByVal e As EventArgs)
' Fires upon attempting to authenticate the user
Dim formsAuthTicket As FormsAuthenticationTicket
Dim httpcook As HttpCookie
Dim genIdentity As GenericIdentity
Dim roles() As String
Dim genPrincipal As GenericPrincipal
httpcook = Context.Request.Cookies("authCookie ")
formsAuthTicket = FormsAuthentication.Decrypt(httpcook.Value)
genIdentity = New GenericIdentity(formsAuthTicket.Name())
roles = formsAuthTicket.UserData.Split("|") 'if more than one role
genPrincipal = New GenericPrincipal(genIdentity, roles)
'The principal object thus created would be accessed
'in the ‘application pages before allowing access
'to the individual functionality
HttpContext.Current.User = genPrincipal
End Sub
现在,在设置了 Principal 对象之后,让我们转到 Web 页面进行用户授权。在 Web 页面(_PageA.aspx_)的 Form_Load
事件中,假设我们只允许角色为“Managers”的用户访问。以下代码将执行授权检查:
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim genPrincipal As IPrincipal
' Access the principal object from HTTPContext
genPrincipal = HttpContext.Current.User
If genPrincipal.IsInRole("Managers") = False then
Response.Write("You do not have access to this Page.")
' Now the controls on the form can be hidden
' or the user can be redirected
' to a different page
' < Code to implement the above goes here >
End if
End Sub
上述代码不会授予任何不属于“Managers”角色/组的用户访问 _PageA.aspx_ 页面的权限。
总而言之,这种安全模型最适合需要实现基于角色的安全的应用,其中角色由应用程序定义,而身份验证由 Windows 执行。
场景 B:自定义身份验证(Identity)与数据库提供的角色
此场景最适合用户和角色都在应用程序中定义的应用程序。此类应用程序将有一个登录页面/窗体,接受用户名和密码并执行身份验证。除了身份验证机制外,场景 A 中解释的相同代码也可用于此场景。身份验证将随后通过验证用户名和密码与数据存储的匹配,并继续使用 GenericIdentity
和从数据库检索的角色来构建 GenericPrincipal 对象。
场景 C:Windows 身份验证(Identity)与 Windows 组成员身份作为角色
此场景最适合依赖 Windows 进行身份和角色的应用程序。身份将是 Windows 用户名,角色将是 Windows 用户的组成员身份。基于角色的安全概念保持不变,与场景 A 中提到的代码类似。我们不需要创建 GenericPrincipal
对象,而是需要创建 WindowsPrincipal
对象,并提供 WindowsIdentity
。此外,由于我们依赖 Windows 组成员身份作为角色,因此无需从数据存储中获取“角色”。在每个页面/窗体中执行的检查保持不变。
场景分析
在上述场景中,我们所做的就是构建包含身份和角色的 Principal 对象。页面/窗体不必担心构建 Principal 对象。它们只需要根据应用程序设置的授权规则执行“IsInRole”检查。
这清楚地展示了在采用 Identity 和 Principal 对象来实现安全性时,身份验证和授权之间的明确分离。此外,在应用程序的整个生命周期中,我们都在处理封装身份、角色并持久化的对象。
结论
这些场景仅演示了 Principal 和 Identity 对象的使用。它们并不意味着对其使用的任何规则。选择这些场景是为了更好地理解概念。总而言之,Principal 和 Identity 对象为在 .NET 中实现任何应用程序的安全性提供了一种手段。
使用代码
本文中提到的代码片段是随本文提供的源代码的一部分。下载源代码(Zip 文件)后,将其解压缩到 Web 文件夹(_wwwroot_)中,即可成功打开解决方案。在 Web 应用程序的 IIS 设置中,确保在 _目录安全性_ 选项卡中未选中匿名访问。