Windows SSPI API是Windows安全服务的接口,允许您相互验证客户端和服务器。该API的主要用途之一是提供Windows集成身份验证(即单点登录) - 应用程序能够通过使用用户登录时的凭据自动向用户验证服务器。
此过程的正常流程:
AcquireCredentialsHandle()
返回的句柄使用AcquireCredentialsHandle()
API参与服务器的SSPI身份验证周期。InitializeSecurityContext()
(客户端)和InitializeSecurityContext()
(服务器端)API交换不透明的字节数组('tokens')以验证彼此的令牌并继续进行身份验证周期。当使用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
参数(上面的空值)可以是对AcceptSecurityContext()
结构的引用,它允许您直接指定用户名和密码。
我尝试以这种方式调用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
);
答案 0 :(得分:1)
AcquireCredentialsHandle()
也会返回true;仅当客户端尝试调用InitializeSecurityContext()
时,API才会验证用户名和密码。 AcquireCredentialsHandle()
只执行参数验证(指针值有效,结构填写正确,参数彼此有意义等)。由于我正确提供了错误的参数,AcquireCredentialsHandle()
并不关心。
...
总而言之,客户端应用程序参与SSPI身份验证的正常周期如下:
AcquireCredentialsHandle()
。InitializeSecurityContext()
,它将一些令牌返回给服务器。AcceptSecurityContext()
,返回令牌以发送回客户端。AcquireCredentialsHandle()
。在上述情况下,通过使用提供的AcquireCredentialsHandle()
结构调用SEC_WINNT_AUTH_IDENTITY
获取的凭据(依次填写有效的用户名,域名和密码)未在客户端验证一直到客户端第一次调用InitializeSecurityContext()
,然后才将其第一个令牌发送到服务器。
回答类似的问题,Dave Christiansen(微软员工?)posted the following在新闻组&microsoft.public.platformsdk.security' 2003年9月19日:
您如何确定凭据是本地凭据 用户? SSPI有时候很棘手。你正在使用什么包 (如果您正在使用,听起来像NTLM,Kerberos或Negotiate SEC_WINNT_AUTH_IDENTITY)?
请注意,即使是AcquireCredentialsHandle也会成功 给出的凭据不正确(例如密码错误),因为它 在你调用InitializeSecurityContext之前,它们实际上并没有使用它们。