【发布时间】:2017-12-30 15:31:46
【问题描述】:
背景
Windows SSPI API 是 Windows 安全服务的接口,允许您相互验证客户端和服务器。 API 的主要用途之一是提供 Windows 集成身份验证,即单点登录 - 应用程序能够使用用户登录时的凭据自动对服务器进行身份验证。
这个过程的正常流程:
- 用户通常使用用户名和密码登录到他们的计算机。
- 用户运行一个应用程序,该应用程序使用 SSPI 对用户进行服务验证。
- 该应用程序调用
AcquireCredentialsHandle()以获取用户登录时创建的现有凭据的句柄。 - 应用程序使用
AcquireCredentialsHandle()返回的句柄通过InitializeSecurityContext()API 参与与服务器的SSPI 身份验证周期。 - 应用程序和服务器使用
InitializeSecurityContext()(客户端)和AcceptSecurityContext()(服务器端)API 交换不透明的字节数组(“令牌”),以验证彼此的令牌并继续身份验证周期。李> - 当来回传递足够多的令牌时,身份验证周期会在某个时间点完成。
- 应用程序已代表用户向服务器验证了自己。身份验证完成。
这是使用 API 作为单点登录的一部分时此过程的正常流程。
AcquireCredentialsHandle() 的签名是:
SECURITY_STATUS SEC_Entry AcquireCredentialsHandle(
_In_ SEC_CHAR *pszPrincipal,
_In_ SEC_CHAR *pszPackage,
_In_ ULONG fCredentialUse,
_In_ PLUID pvLogonID,
_In_ PVOID pAuthData,
_In_ SEC_GET_KEY_FN pGetKeyFn,
_In_ PVOID pvGetKeyArgument,
_Out_ PCredHandle phCredential,
_Out_ PTimeStamp ptsExpiry
);
在上述典型的 Windows 集成身份验证案例中使用时,通常不提供 pAuthData 参数 - 而是提供空引用。
问题
我希望能够以直接向其提供用户名和密码的方式使用AcquireCredentialsHandle()。它似乎可以处理这个问题,因为pAuthData 参数(上面为空)可以引用SEC_WINNT_AUTH_IDENTITY 结构,它允许您直接指定用户名和密码。
我试图以这种方式调用AcquireCredentialsHandle(),并为它提供一个填写完整的SEC_WINNT_AUTH_IDENTITY 结构以及我的用户名和密码。但是,每次调用它时,我都会返回成功,即使我使用虚构的用户名或密码。作为健全性检查,我尝试使用相同的凭据调用 LogonUser(),它们要么工作要么失败,正如预期的那样,这取决于我是否给了它一个有效的用户名/密码组合。
我做错了什么? 为什么AcquireCredentialsHandle() 总是返回成功,即使凭据完全不正确?
下面显示了我用来调用 AcquireCredentialsHandle() 的代码的基础知识:
public class SuppliedCredential : Credential
{
public SuppliedCredential( string securityPackage, CredentialUse use, string username, string domain, string password ) :
base( securityPackage )
{
Init( use, username, domain, password );
}
private void Init( CredentialUse use, string username, string domain, string password )
{
// Copy off for the call, since this.SecurityPackage is a property.
string packageName = this.SecurityPackage;
TimeStamp rawExpiry = new TimeStamp();
SecurityStatus status = SecurityStatus.InternalError;
AuthIdentity auth = new AuthIdentity();
auth.User = username;
auth.UserLength = username.Length;
auth.Domain = domain;
auth.DomainLength = domain.Length;
auth.Password = password;
auth.PasswordLength = password.Length;
auth.Flags = 0x2; // unicode
this.Handle = new SafeCredentialHandle();
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
status = CredentialNativeMethods.AcquireCredentialsHandle_2(
"",
packageName,
use,
IntPtr.Zero,
ref auth,
IntPtr.Zero,
IntPtr.Zero,
ref this.Handle.rawHandle,
ref rawExpiry
);
}
if( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to call AcquireCredentialHandle", status );
}
this.Expiry = rawExpiry.ToDateTime();
}
}
...
[StructLayout( LayoutKind.Sequential )]
public struct AuthIdentity
{
[MarshalAs(UnmanagedType.LPWStr)]
public string User;
public int UserLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string Domain;
public int DomainLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string Password;
public int PasswordLength;
public int Flags;
};
...
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcquireCredentialsHandle_2(
string principleName,
string packageName,
CredentialUse credentialUse,
IntPtr loginId,
ref AuthIdentity packageData,
IntPtr getKeyFunc,
IntPtr getKeyData,
ref RawSspiHandle credentialHandle,
ref TimeStamp expiry
);
【问题讨论】:
标签: c# windows authentication winapi sspi