在 ASP Net 中使用 EventLog
在 ASP Net 中使用 EventLog
引言
这里提供一种解决方案,适用于需要了解客户端错误或事件,并将所有日志存储在同一台计算机上的 Web 或桌面开发人员。 .Net framework 2.0 在使用系统 EventLog 时存在一些不受支持的功能。 例如,您无法使用 EventLog 类中的方法填充“用户”字段。 当然,您可以使用 Log4net 或其他第三方开源库。 但有时这可能会导致 DLL 地狱,或者客户不接受第三方库。 因此,您需要找到一种不错的日志记录解决方案,可以轻松地进行筛选等等。
背景
我将使用非托管代码来获得所需的结果。 我不建议使用这种实现方式。 这仅适用于无法将目标框架从 2.0 升级到 3.0 或 3.5 的情况。使用代码
我的简单应用程序能够将两种类型的消息放入事件日志中:带用户名和不带用户名。
当您使用标准 EventLog 方法时,您应该在系统日志中看到下一条消息。
如您所见,“用户”字段(标记为红色)未填充。 现在我们将尝试添加它。
首先,您需要导入以下函数并声明以下结构和变量。
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function RegisterEventSource(ByVal machine As String, ByVal source As String) As IntPtr
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function DeregisterEventSource(ByVal handle As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function ReportEvent(ByVal hHandle As IntPtr,
ByVal wType As UShort, ByVal wCategory As UShort, ByVal dwEventID As
UInteger, ByVal uSid As IntPtr, ByVal wStrings As UShort, _
ByVal dwDataSize As UInteger, ByVal lpStrings As String(), ByVal bData As Byte) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function GetTokenInformation(ByVal handle As
IntPtr, ByVal token As TOKEN_INFORMATION_CLASS, ByVal uSid As IntPtr,
ByVal size As UInteger, ByRef tokenSize As UInteger) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function OpenEventLog(ByVal lpUNCServerName As String, ByVal lpSourceName As String) As IntPtr
End Function
Protected Declare Function LogonUserA Lib "advapi32.dll" (ByVal lpszUsername As String, _
ByVal lpszDomain As String, _
ByVal lpszPassword As String, _
ByVal dwLogonType As Integer, _
ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Integer
Protected Declare Auto Function DuplicateToken Lib "advapi32.dll" ( _
ByVal ExistingTokenHandle As IntPtr, _
ByVal ImpersonationLevel As Integer, _
ByRef DuplicateTokenHandle As IntPtr) As Integer
Protected Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
Protected Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long
#End Region
#Region "API adaptation"
Protected Enum TOKEN_INFORMATION_CLASS
TokenUser = 1
TokenGroups
TokenPrivileges
TokenOwner
TokenPrimaryGroup
TokenDefaultDacl
TokenSource
TokenType
TokenImpersonationLevel
TokenStatistics
TokenRestrictedSids
TokenSessionId
TokenGroupsAndPrivileges
TokenSessionReference
TokenSandBoxInert
TokenAuditPolicy
TokenOrigin
End Enum
Protected Structure TOKEN_USER
Public User As SID_AND_ATTRIBUTES
End Structure
Protected Structure SID_AND_ATTRIBUTES
Public Sid As IntPtr
Public Attributes As Integer
End Structure
Private LOGON32_LOGON_INTERACTIVE As Integer = 2
Private LOGON32_PROVIDER_DEFAULT As Integer = 0
#End Region
现在我们可以将 UserName 添加到该字段了。
Dim eventSrcHandle As IntPtr = RegisterEventSource(Nothing, iSource)
Dim tokenInfoSize As UInteger = 0
Dim userTokenPtr As IntPtr = WindowsIdentity.GetCurrent().Token
'using this first call, get the length required to hold the token information in the tokenInfoSize parameter
Dim bresult As Boolean = GetTokenInformation(userTokenPtr, TOKEN_INFORMATION_CLASS.TokenUser, IntPtr.Zero, tokenInfoSize, tokenInfoSize)
Dim [error] As Integer = Marshal.GetLastWin32Error()
Dim userTokenInfo As IntPtr = Marshal.AllocHGlobal(CInt(tokenInfoSize))
'get the user token now with the pointer allocated to the right size
bresult = GetTokenInformation(userTokenPtr, TOKEN_INFORMATION_CLASS.TokenUser, userTokenInfo, tokenInfoSize, tokenInfoSize)
If bresult Then
Dim tokUser As TOKEN_USER = DirectCast(Marshal.PtrToStructure(userTokenInfo, GetType(TOKEN_USER)), TOKEN_USER)
Dim message As String() = New String(0) {}
message(0) = iMessage
bresult = ReportEvent(eventSrcHandle, iType, iCategory, iEventID, tokUser.User.Sid, 1, _
0, message, New Byte())
End If
'Clean up
DeregisterEventSource(eventSrcHandle)
Marshal.FreeHGlobal(userTokenInfo)
在 ASP.net 网站上,如果我们以这种方式记录错误,我们应该在系统日志中看到以下内容。
这看起来比以前的好多了。 但是,如果我们想记录对我们的 Web 应用程序执行某些操作的用户呢?
在这里,我们必须深入了解 UserTokens。
因此,第一点是,如果当前域中没有用户 'X',我们就无法创建带有用户的事件。
第二点,如果我们的站点运行在非服务器操作系统(例如 Windows XP)的计算机上,我们必须知道密码。
第三点,如果我们的计算机是 Windows 2003 或类似系统,您可以使用 UPN 用户名 'name@domainname.machinename.com'(UPN 可能如下所示)获取 UserToken。 您不需要导入 LogonUserA 函数来获取 UserToken。 您可以使用 System.Security.Principal 命名空间来获取它
最后。 系统调用 ReportEvent 将使用进程所有者的 UserName 登录事件。 因此,要更改 UserName,您需要进行身份模拟。 下面的代码片段可能会有所帮助。
If impersonateValidUser("user", "domain", "password") Then
undoImpersonation()
Else
''TODO support impersonation fault
End If
Private Function impersonateValidUser(ByVal userName As String, _
ByVal domain As String, ByVal password As String) As Boolean
Dim tempWindowsIdentity As WindowsIdentity
Dim token As IntPtr = IntPtr.Zero
Dim tokenDuplicate As IntPtr = IntPtr.Zero
impersonateValidUser = False
If RevertToSelf() Then
If LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
If DuplicateToken(token, 2, tokenDuplicate) <> 0 Then
tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
impersonationContext = tempWindowsIdentity.Impersonate()
If Not impersonationContext Is Nothing Then
impersonateValidUser = True
End If
End If
End If
End If
If Not tokenDuplicate.Equals(IntPtr.Zero) Then
CloseHandle(tokenDuplicate)
End If
If Not token.Equals(IntPtr.Zero) Then
CloseHandle(token)
End If
End Function
兴趣点
这不仅可以用于日志记录。 我认为这段代码可以用于在 AD 中执行任何类型的安全操作。
历史
版本 1.0.0